UNPKG

webext-typed-messages

Version:

Better WebExtension sendMessage API

175 lines (164 loc) 4.33 kB
/** * Definitions for your custom runtime messages. * * Should be a map of message types to message handler functions. * * @example * * ```ts * declare module "webext-typed-messages" { * interface RuntimeMessages { * hello: (name: string) => string; * } * } * ``` */ export interface RuntimeMessages {} /** * Definitions for your custom tab messages. * * Should be a map of message types to message handler functions. * * @example * * ```ts * declare module "webext-typed-messages" { * interface TabMessages { * add: (a: number, b: number) => Promise<number>; * } * } * ``` */ export interface TabMessages {} /** * Parameters for a message handler. * * @template T - Message handler map * @template M - Message type */ type messageParams<T extends {}, M extends keyof T> = T[M] extends ( ...params: infer P ) => any ? P : never; /** * Return type of a message handler. * * @template T - Message handler map * @template M - Message type */ type messageResult<T extends {}, M extends keyof T> = T[M] extends ( ...params: any[] ) => infer R ? Awaited<R> : undefined; /** * Sends a runtime message to your registered handlers. * * Call {@link addRuntimeMessageListener} to register your handlers. * * @see {@link chrome.runtime.sendMessage} */ export function sendRuntimeMessage<M extends keyof RuntimeMessages>( type: M, ...params: messageParams<RuntimeMessages, M> ): Promise<messageResult<RuntimeMessages, M> | undefined> { return new Promise((resolve, reject) => { chrome.runtime.sendMessage({ type, params }, (response) => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); } else { resolve(response); } }); }); } /** * Sends a message to the content script in the specified tab. * * The content script should call {@link addTabMessageListener} to receive the message. * * @see {@link chrome.tabs.sendMessage} */ export function sendTabMessage<M extends keyof TabMessages>( tabId: number, type: M, ...params: messageParams<TabMessages, M> ): Promise<messageResult<TabMessages, M> | undefined> { return new Promise((resolve, reject) => { chrome.tabs.sendMessage(tabId, { type, params }, (response) => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); } else { resolve(response); } }); }); } /** * Object of functions for handling messages of each type. * * @template T - Message handler map */ type MessageListenerMap<T extends {}> = { [M in keyof T]: ( this: void, ...params: messageParams<T, M> ) => messageResult<T, M> | Promise<messageResult<T, M>>; }; /** * Registers listeners for runtime messages. * * Should be called in the background script. * * @see {@link chrome.runtime.onMessage} */ export function addRuntimeMessageListener( listenerMap: MessageListenerMap<RuntimeMessages> ) { const messageListener = getMessageListener(listenerMap); chrome.runtime.onMessage.addListener(messageListener); return function removeMessageListener() { chrome.runtime.onMessage.removeListener(messageListener); }; } /** * Registers listeners for tab messages. * * Should be called in the content script. * * @see {@link chrome.runtime.onMessage} */ export function addTabMessageListener( listenerMap: MessageListenerMap<TabMessages> ) { const messageListener = getMessageListener(listenerMap); chrome.runtime.onMessage.addListener(messageListener); return function removeMessageListener() { chrome.runtime.onMessage.removeListener(messageListener); }; } function getMessageListener<T extends {}>(listenerMap: MessageListenerMap<T>) { return function messageListener( message: any, sender: chrome.runtime.MessageSender, sendResponse: (response: any) => void ) { handleMessage(listenerMap, message, sendResponse); return true; }; } async function handleMessage( listenerMap: Record<string, (...params: any[]) => any>, message: any, sendResponse: (response: any) => void ) { const listener = listenerMap[message.type]; try { const result = await listener?.(...message.params); sendResponse(result); } catch (error) { sendResponse(undefined); throw error; } }