UNPKG

@junioryono/async-post-message

Version:

Asynchronous postMessage protocol for typed, promise-based windows and iFrame communication

1 lines 8.63 kB
{"version":3,"sources":["../src/ParentHandler.ts","../src/RequestManager/RequestManager.ts","../src/WebViewRequester.ts"],"sourcesContent":["import {\n AsyncPostMessageRequest,\n AsyncPostMessageResponse,\n Promises,\n} from \"./RequestManager/types\";\n\n/**\n * Type for unsubscribing from an event listener.\n */\ntype Unsubscribe = () => void;\n\n/**\n * Handles a requests from the webview using the given callback. This code should be run on the\n * parent context.\n *\n * @param {object} target - The target object that has a postMessage method (ie. iframe).\n * @param {function} handler - The handler function that takes an AsyncPostMessageRequest and\n * returns a Promise of AsyncPostMessageResponse.\n * @return {function} - The unsubscribe function that removes the event listener. Useful for React.\n */\nexport const handleWebViewRequest = <PromisesInterface extends Promises>(\n target: { postMessage: typeof window.postMessage },\n handler: (\n request: AsyncPostMessageRequest<PromisesInterface>\n ) => Promise<AsyncPostMessageResponse<PromisesInterface>>\n): Unsubscribe => {\n const handleMessage = async (\n event: MessageEvent<AsyncPostMessageRequest<PromisesInterface>>\n ) => {\n // Execute the business logic.\n const response = await handler(event.data);\n\n // Send the response back to the iFrame so it can resolve.\n target.postMessage(response, \"*\");\n };\n\n window.addEventListener(\"message\", handleMessage);\n\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n };\n};\n","import { v4 as uuidv4 } from \"uuid\";\nimport { AsyncPostMessageRequest, AsyncPostMessageResponse, CallbackType, Promises, UidType } from \"./types\";\n\n/**\n * A class for managing async postMessage requests and their promise wrappers. Each instance\n * stores a map of callbacks for each unique ID. It does not wrap the `postMessage` API. Instead,\n * it allows the client to set the listener and postMessage methods on their own.\n */\nexport class RequestManager<PromisesInterface extends Promises> {\n /**\n * Map of callbacks for each promise's unique ID.\n */\n private callbacks: Map<UidType, CallbackType>;\n\n constructor() {\n this.callbacks = new Map();\n }\n\n /**\n * Send a strongly typed async postMessage request.\n *\n * @param functionName The name of the function to call\n * @param args The arguments to pass to the function\n * @param options Options for the request. Defaults to a timeout of 10 seconds. If `0` is passed\n * as the timeout, the request will not timeout.\n * @returns A promise that resolves with the return value of the function\n */\n public send = <FnNameType extends keyof PromisesInterface & string>(\n functionName: FnNameType,\n args: Parameters<PromisesInterface[FnNameType]>,\n options?: { timeoutMs: number }\n ): Promise<ReturnType<PromisesInterface[FnNameType]>> => {\n // Generate a unique ID for this request.\n const uid = uuidv4();\n\n const promise = new Promise<ReturnType<PromisesInterface[FnNameType]>>((resolve, reject) => {\n this.callbacks.set(uid, { resolve, reject });\n const message: AsyncPostMessageRequest<PromisesInterface> = {\n uid,\n functionName,\n args,\n };\n this.postMessage(message);\n\n // Set a timeout for the request (defaults to 10 seconds).\n const timeout = options?.timeoutMs ?? 10000;\n if (timeout > 0) {\n setTimeout(() => {\n reject(new Error(`${functionName} timed out (id: ${uid})`));\n this.callbacks.delete(uid);\n }, timeout);\n }\n });\n\n return promise;\n };\n\n /**\n * Handler for postMessages to the client.\n *\n * @param message Message payload received by the webview.\n */\n public onResponse(message: AsyncPostMessageResponse<PromisesInterface>): void {\n const callback = this.callbacks.get(message.uid);\n\n // If the callback is not defined, do nothing as it means the request timed out.\n if (!callback) {\n return;\n }\n\n this.callbacks.delete(message.uid);\n if (message.error) {\n callback.reject(new Error(message.error));\n } else {\n callback.resolve(message.response);\n }\n }\n\n /**\n * Send a strongly typed async postMessage request to the target. This should be set by the\n * client.\n *\n * @param _message Message payload to send to the target.\n */\n public postMessage(_message: AsyncPostMessageRequest<PromisesInterface>): void {\n console.warn(\"[AsyncPostMessage] postMessage unimplemented\");\n }\n}\n","import { RequestManager } from \"./RequestManager/RequestManager\";\nimport { AsyncPostMessageResponse, Promises } from \"./RequestManager/types\";\n\n/**\n * The Singleton class defines the `getInstance` method that lets clients access\n * the unique singleton instance.\n */\nexport class WebViewRequester<PromisesInterface extends Promises = any> {\n private static instance: WebViewRequester;\n\n private requestManager: RequestManager<PromisesInterface>;\n\n /**\n * The Singleton's constructor should always be private to prevent direct\n * construction calls with the `new` operator.\n */\n private constructor() {\n // Setup instance of RequestManager that will be used by all calls.\n this.requestManager = new RequestManager();\n\n // Listen for messages from the parent.\n window.addEventListener(\n \"message\",\n (event: MessageEvent<AsyncPostMessageResponse<PromisesInterface>>) => {\n if (!event.data) {\n return;\n }\n this.requestManager.onResponse(event.data);\n }\n );\n\n // Setup data pipeline to send messages to the parent.\n this.requestManager.postMessage = (message) => {\n window.parent.postMessage(message, \"*\");\n };\n }\n\n /**\n * The static method that controls the access to the global AsyncPostMessage instance.\n *\n * We use a singleton so that the client does not need to worry about creating multiple\n * instances of the `AsyncPostMessage` class.\n */\n public static getInstance = <\n PromisesInterface extends Promises\n >(): WebViewRequester<PromisesInterface> => {\n if (!WebViewRequester.instance) {\n WebViewRequester.instance = new WebViewRequester();\n }\n\n return WebViewRequester.instance;\n };\n\n /**\n * Execute a strongly typed promise that is executed in the parent process.\n */\n public execute = <FnNameType extends keyof PromisesInterface & string>(\n functionName: FnNameType,\n args: Parameters<PromisesInterface[FnNameType]>,\n options?: { timeoutMs: number }\n ): Promise<ReturnType<PromisesInterface[FnNameType]>> => {\n return this.requestManager.send(functionName, args, options);\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAoBO,IAAM,uBAAuB,CAClC,QACA,YAGgB;AAChB,QAAM,gBAAgB,CACpB,UACG;AAEH,UAAM,WAAW,MAAM,QAAQ,MAAM,IAAI;AAGzC,WAAO,YAAY,UAAU,GAAG;AAAA,EAClC;AAEA,SAAO,iBAAiB,WAAW,aAAa;AAEhD,SAAO,MAAM;AACX,WAAO,oBAAoB,WAAW,aAAa;AAAA,EACrD;AACF;;;ACzCA,SAAS,MAAM,cAAc;AAQtB,IAAM,iBAAN,MAAyD;AAAA,EAM9D,cAAc;AAad;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAO,OAAO,CACZ,cACA,MACA,YACuD;AAEvD,YAAM,MAAM,OAAO;AAEnB,YAAM,UAAU,IAAI,QAAmD,CAAC,SAAS,WAAW;AAnChG;AAoCM,aAAK,UAAU,IAAI,KAAK,EAAE,SAAS,OAAO,CAAC;AAC3C,cAAM,UAAsD;AAAA,UAC1D;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,aAAK,YAAY,OAAO;AAGxB,cAAM,WAAU,wCAAS,cAAT,YAAsB;AACtC,YAAI,UAAU,GAAG;AACf,qBAAW,MAAM;AACf,mBAAO,IAAI,MAAM,GAAG,YAAY,mBAAmB,GAAG,GAAG,CAAC;AAC1D,iBAAK,UAAU,OAAO,GAAG;AAAA,UAC3B,GAAG,OAAO;AAAA,QACZ;AAAA,MACF,CAAC;AAED,aAAO;AAAA,IACT;AAxCE,SAAK,YAAY,oBAAI,IAAI;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8CO,WAAW,SAA4D;AAC5E,UAAM,WAAW,KAAK,UAAU,IAAI,QAAQ,GAAG;AAG/C,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAEA,SAAK,UAAU,OAAO,QAAQ,GAAG;AACjC,QAAI,QAAQ,OAAO;AACjB,eAAS,OAAO,IAAI,MAAM,QAAQ,KAAK,CAAC;AAAA,IAC1C,OAAO;AACL,eAAS,QAAQ,QAAQ,QAAQ;AAAA,IACnC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,YAAY,UAA4D;AAC7E,YAAQ,KAAK,8CAA8C;AAAA,EAC7D;AACF;;;AChFO,IAAM,oBAAN,MAAM,kBAA2D;AAAA;AAAA;AAAA;AAAA;AAAA,EAS9D,cAAc;AAwCtB;AAAA;AAAA;AAAA,SAAO,UAAU,CACf,cACA,MACA,YACuD;AACvD,aAAO,KAAK,eAAe,KAAK,cAAc,MAAM,OAAO;AAAA,IAC7D;AA5CE,SAAK,iBAAiB,IAAI,eAAe;AAGzC,WAAO;AAAA,MACL;AAAA,MACA,CAAC,UAAqE;AACpE,YAAI,CAAC,MAAM,MAAM;AACf;AAAA,QACF;AACA,aAAK,eAAe,WAAW,MAAM,IAAI;AAAA,MAC3C;AAAA,IACF;AAGA,SAAK,eAAe,cAAc,CAAC,YAAY;AAC7C,aAAO,OAAO,YAAY,SAAS,GAAG;AAAA,IACxC;AAAA,EACF;AA4BF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAxDa,kBAoCG,cAAc,MAEgB;AAC1C,MAAI,CAAC,kBAAiB,UAAU;AAC9B,sBAAiB,WAAW,IAAI,kBAAiB;AAAA,EACnD;AAEA,SAAO,kBAAiB;AAC1B;AA5CK,IAAM,mBAAN;","names":[]}