@knowledgecode/messenger
Advanced tools
| type Endpoint = Window | Worker; | ||
| type TopicListener<T = unknown> = (data?: T) => unknown; | ||
| type Packet<T = unknown> = { | ||
| method: 'ack'; | ||
| payload: { | ||
| client: string; | ||
| }; | ||
| } | { | ||
| method: 'reply'; | ||
| payload: { | ||
| id: string; | ||
| topic: string; | ||
| data?: T; | ||
| error?: Error; | ||
| }; | ||
| } | { | ||
| method: 'publish'; | ||
| payload: { | ||
| topic: string; | ||
| data?: T; | ||
| }; | ||
| } | { | ||
| method: 'connect'; | ||
| name: string; | ||
| } | { | ||
| method: 'disconnect'; | ||
| payload: { | ||
| client: string; | ||
| }; | ||
| } | { | ||
| method: 'send'; | ||
| payload: { | ||
| topic: string; | ||
| data?: T; | ||
| }; | ||
| } | { | ||
| method: 'request'; | ||
| payload: { | ||
| client: string; | ||
| id: string; | ||
| topic: string; | ||
| data?: T; | ||
| }; | ||
| } | { | ||
| method: 'subscribe'; | ||
| payload: { | ||
| client: string; | ||
| topic: string; | ||
| }; | ||
| } | { | ||
| method: 'unsubscribe'; | ||
| payload: { | ||
| client: string; | ||
| topic: string; | ||
| }; | ||
| }; | ||
| declare class Server { | ||
| private readonly _clients; | ||
| private readonly _listeners; | ||
| private readonly _subscribers; | ||
| private _endpoint; | ||
| private readonly _accept; | ||
| constructor(name: string, endpoint?: Endpoint); | ||
| _listener(evt: MessageEvent<Packet>): void; | ||
| _postMessage(client: string, message: Packet): void; | ||
| bind<T = unknown>(topic: string, listener: TopicListener<T>): boolean; | ||
| unbind(topic: string): void; | ||
| publish(topic: string, data?: unknown): void; | ||
| close(): void; | ||
| } | ||
| interface ClientOptions { | ||
| targetOrigin?: string; | ||
| timeout?: number; | ||
| } | ||
| declare class Client { | ||
| private _id; | ||
| private _port; | ||
| private _connected; | ||
| private readonly _reps; | ||
| private readonly _subscribers; | ||
| constructor(); | ||
| _listener(evt: MessageEvent<Packet>): void; | ||
| _postMessage(message: Packet): void; | ||
| connect(name: string, endpoint?: Endpoint, options?: ClientOptions): Promise<void>; | ||
| disconnect(): void; | ||
| send(topic: string, data: unknown): void; | ||
| req<T = unknown>(topic: string, data?: unknown, timeout?: number): Promise<T>; | ||
| subscribe<T = unknown>(topic: string, listener: TopicListener<T>): void; | ||
| unsubscribe(topic?: string, listener?: TopicListener): void; | ||
| } | ||
| export { Client as MessengerClient, Server as MessengerServer }; |
| /** | ||
| * @license | ||
| * Copyright 2019 KNOWLEDGECODE | ||
| * SPDX-License-Identifier: MIT | ||
| */ | ||
| class e{constructor(){this._id=void 0,this._port=void 0,this._connected=void 0,this._reps=new Map,this._subscribers=new Map}_listener(e){switch(e.data.method){case"reply":{const{id:t,data:s,error:i}=e.data.payload,o=this._reps.get(t);o&&(o.timerId&&clearTimeout(o.timerId),i?o.reject(i):o.resolve(s),this._reps.delete(t));break}case"publish":{const{topic:t,data:s}=e.data.payload,i=this._subscribers.get(t);if(i)for(const e of i.values())e(s);break}}}_postMessage(e){var t;null==(t=this._port)||t.postMessage(e)}connect(e,t=self,s={}){return this._connected?this._connected:"postMessage"in t?(this._connected=new Promise((i,o)=>{const{port1:r,port2:n}=new MessageChannel,{targetOrigin:a,timeout:c}=s,d=(null!=c?c:0)>0?setTimeout(()=>{var e;null==(e=this._port)||e.close(),this._port=void 0,this._connected=void 0,o(new Error("Connection timed out."))},c):0,h={method:"connect",name:e};this._port=r,this._port.onmessage=e=>{const{method:t}=e.data;this._port&&"ack"===t&&(this._id=e.data.payload.client,this._port.onmessage=e=>this._listener(e),d&&clearTimeout(d),i())},t.postMessage(h,{targetOrigin:null!=a?a:"*",transfer:[n]})}),this._connected):Promise.reject(new Error("The endpoint has no postMessage method."))}disconnect(){this.unsubscribe();for(const e of this._reps.values())clearTimeout(e.timerId);this._reps.clear(),this._id&&this._port&&(this._postMessage({method:"disconnect",payload:{client:this._id}}),this._port.close(),this._port=void 0,this._id=void 0),this._connected=void 0}send(e,t){if(!this._port)throw new Error("Not connected.");this._postMessage({method:"send",payload:{topic:e,data:t}})}req(e,t,s=0){return new Promise((i,o)=>{if(!this._id||!this._port)return void o(new Error("Not connected."));const r=Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2),n=s>0?self.setTimeout(()=>{const e=this._reps.get(r);e&&(e.reject(new Error("Request timed out.")),this._reps.delete(r))},s):0;this._reps.set(r,{resolve:i,reject:o,timerId:n}),this._postMessage({method:"request",payload:{client:this._id,id:r,topic:e,data:t}})})}subscribe(e,t){var s;if(!this._id||!this._port)throw new Error("Not connected.");const i=null!=(s=this._subscribers.get(e))?s:new Set;i.add(t),this._subscribers.set(e,i),this._postMessage({method:"subscribe",payload:{client:this._id,topic:e}})}unsubscribe(e,t){if(e)if(t){const s=this._subscribers.get(e);s&&s.delete(t)}else this._subscribers.delete(e);else this._subscribers.clear()}}class t{constructor(e,t=self){this._clients=new Map,this._listeners=new Map,this._subscribers=new Map,this._endpoint=t,this._accept=t=>{if("connect"!==t.data.method||t.data.name!==e)return;t.stopImmediatePropagation();const[s]=t.ports,i=Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2);s.onmessage=e=>this._listener(e),this._clients.set(i,s),s.postMessage({method:"ack",payload:{client:i}})},this._endpoint.addEventListener("message",this._accept)}_listener(e){var t,s,i,o;switch(e.data.method){case"send":{const{topic:s,data:i}=e.data.payload;null==(t=this._listeners.get(s))||t(i);break}case"request":{const{client:t,id:s,topic:i,data:r}=e.data.payload,n=this._listeners.get(i);n?(o=n(r),o instanceof Promise?o:Promise.resolve(o)).then(e=>this._postMessage(t,{method:"reply",payload:{id:s,topic:i,data:e}})).catch(e=>this._postMessage(t,{method:"reply",payload:{id:s,topic:i,error:e instanceof Error?e:new Error(String(e))}})):this._postMessage(t,{method:"reply",payload:{id:s,topic:i,error:new Error("Topic is not bound.")}});break}case"subscribe":{const{client:t,topic:i}=e.data.payload,o=null!=(s=this._subscribers.get(i))?s:new Set;o.add(t),this._subscribers.set(i,o);break}case"unsubscribe":{const{client:t,topic:s}=e.data.payload,i=this._subscribers.get(s);null==i||i.delete(t);break}case"disconnect":{const{client:t}=e.data.payload;null==(i=this._clients.get(t))||i.close(),this._clients.delete(t);for(const e of this._subscribers.values())e.delete(t);break}}}_postMessage(e,t){const s=this._clients.get(e);null==s||s.postMessage(t)}bind(e,t){return!this._listeners.has(e)&&(this._listeners.set(e,t),!0)}unbind(e){this._listeners.delete(e)}publish(e,t){const s=this._subscribers.get(e);if(s){const i={method:"publish",payload:{topic:e,data:t}};for(const e of s.values())this._postMessage(e,i)}}close(){for(const e of this._clients.values())e.close();this._clients.clear(),this._listeners.clear(),this._subscribers.clear(),this._endpoint&&(this._endpoint.removeEventListener("message",this._accept),this._endpoint=void 0)}}export{e as MessengerClient,t as MessengerServer}; |
| /** | ||
| * @license | ||
| * Copyright 2019 KNOWLEDGECODE | ||
| * SPDX-License-Identifier: MIT | ||
| */ | ||
| !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).messenger={})}(this,function(e){"use strict";e.MessengerClient=class{constructor(){this._id=void 0,this._port=void 0,this._connected=void 0,this._reps=new Map,this._subscribers=new Map}_listener(e){switch(e.data.method){case"reply":{const{id:t,data:s,error:i}=e.data.payload,o=this._reps.get(t);o&&(o.timerId&&clearTimeout(o.timerId),i?o.reject(i):o.resolve(s),this._reps.delete(t));break}case"publish":{const{topic:t,data:s}=e.data.payload,i=this._subscribers.get(t);if(i)for(const e of i.values())e(s);break}}}_postMessage(e){var t;null==(t=this._port)||t.postMessage(e)}connect(e,t=self,s={}){return this._connected?this._connected:"postMessage"in t?(this._connected=new Promise((i,o)=>{const{port1:r,port2:n}=new MessageChannel,{targetOrigin:a,timeout:c}=s,d=(null!=c?c:0)>0?setTimeout(()=>{var e;null==(e=this._port)||e.close(),this._port=void 0,this._connected=void 0,o(new Error("Connection timed out."))},c):0,h={method:"connect",name:e};this._port=r,this._port.onmessage=e=>{const{method:t}=e.data;this._port&&"ack"===t&&(this._id=e.data.payload.client,this._port.onmessage=e=>this._listener(e),d&&clearTimeout(d),i())},t.postMessage(h,{targetOrigin:null!=a?a:"*",transfer:[n]})}),this._connected):Promise.reject(new Error("The endpoint has no postMessage method."))}disconnect(){this.unsubscribe();for(const e of this._reps.values())clearTimeout(e.timerId);this._reps.clear(),this._id&&this._port&&(this._postMessage({method:"disconnect",payload:{client:this._id}}),this._port.close(),this._port=void 0,this._id=void 0),this._connected=void 0}send(e,t){if(!this._port)throw new Error("Not connected.");this._postMessage({method:"send",payload:{topic:e,data:t}})}req(e,t,s=0){return new Promise((i,o)=>{if(!this._id||!this._port)return void o(new Error("Not connected."));const r=Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2),n=s>0?self.setTimeout(()=>{const e=this._reps.get(r);e&&(e.reject(new Error("Request timed out.")),this._reps.delete(r))},s):0;this._reps.set(r,{resolve:i,reject:o,timerId:n}),this._postMessage({method:"request",payload:{client:this._id,id:r,topic:e,data:t}})})}subscribe(e,t){var s;if(!this._id||!this._port)throw new Error("Not connected.");const i=null!=(s=this._subscribers.get(e))?s:new Set;i.add(t),this._subscribers.set(e,i),this._postMessage({method:"subscribe",payload:{client:this._id,topic:e}})}unsubscribe(e,t){if(e)if(t){const s=this._subscribers.get(e);s&&s.delete(t)}else this._subscribers.delete(e);else this._subscribers.clear()}},e.MessengerServer=class{constructor(e,t=self){this._clients=new Map,this._listeners=new Map,this._subscribers=new Map,this._endpoint=t,this._accept=t=>{if("connect"!==t.data.method||t.data.name!==e)return;t.stopImmediatePropagation();const[s]=t.ports,i=Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2);s.onmessage=e=>this._listener(e),this._clients.set(i,s),s.postMessage({method:"ack",payload:{client:i}})},this._endpoint.addEventListener("message",this._accept)}_listener(e){var t,s,i,o;switch(e.data.method){case"send":{const{topic:s,data:i}=e.data.payload;null==(t=this._listeners.get(s))||t(i);break}case"request":{const{client:t,id:s,topic:i,data:r}=e.data.payload,n=this._listeners.get(i);n?(o=n(r),o instanceof Promise?o:Promise.resolve(o)).then(e=>this._postMessage(t,{method:"reply",payload:{id:s,topic:i,data:e}})).catch(e=>this._postMessage(t,{method:"reply",payload:{id:s,topic:i,error:e instanceof Error?e:new Error(String(e))}})):this._postMessage(t,{method:"reply",payload:{id:s,topic:i,error:new Error("Topic is not bound.")}});break}case"subscribe":{const{client:t,topic:i}=e.data.payload,o=null!=(s=this._subscribers.get(i))?s:new Set;o.add(t),this._subscribers.set(i,o);break}case"unsubscribe":{const{client:t,topic:s}=e.data.payload,i=this._subscribers.get(s);null==i||i.delete(t);break}case"disconnect":{const{client:t}=e.data.payload;null==(i=this._clients.get(t))||i.close(),this._clients.delete(t);for(const e of this._subscribers.values())e.delete(t);break}}}_postMessage(e,t){const s=this._clients.get(e);null==s||s.postMessage(t)}bind(e,t){return!this._listeners.has(e)&&(this._listeners.set(e,t),!0)}unbind(e){this._listeners.delete(e)}publish(e,t){const s=this._subscribers.get(e);if(s){const i={method:"publish",payload:{topic:e,data:t}};for(const e of s.values())this._postMessage(e,i)}}close(){for(const e of this._clients.values())e.close();this._clients.clear(),this._listeners.clear(),this._subscribers.clear(),this._endpoint&&(this._endpoint.removeEventListener("message",this._accept),this._endpoint=void 0)}}}); |
+52
-32
| { | ||
| "name": "@knowledgecode/messenger", | ||
| "version": "0.5.0", | ||
| "description": "Req/Rep Pub/Sub library for iframes and workers", | ||
| "main": "dist/umd/messenger.js", | ||
| "module": "dist/esm/messenger.mjs", | ||
| "types": "src/index.d.ts", | ||
| "scripts": { | ||
| "build": "rollup -c", | ||
| "test": "npm run build && karma start", | ||
| "test:ts": "tsd -f test/ts" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/knowledgecode/messenger.git" | ||
| }, | ||
| "version": "0.6.0", | ||
| "description": "Type-safe Request/Reply and Pub/Sub messaging library for browser applications", | ||
| "keywords": [ | ||
| "req", | ||
| "rep", | ||
| "pub", | ||
| "sub", | ||
| "request-reply", | ||
| "pub-sub", | ||
| "pubsub", | ||
| "messaging", | ||
| "communication", | ||
| "typescript", | ||
| "type-safe", | ||
| "iframe", | ||
| "worker" | ||
| "worker", | ||
| "browser", | ||
| "message-channel", | ||
| "components" | ||
| ], | ||
| "author": "KNOWLEDGECODE", | ||
| "license": "MIT", | ||
| "homepage": "https://github.com/knowledgecode/messenger#readme", | ||
| "bugs": { | ||
| "url": "https://github.com/knowledgecode/messenger/issues" | ||
| }, | ||
| "homepage": "https://github.com/knowledgecode/messenger#readme", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/knowledgecode/messenger.git" | ||
| }, | ||
| "license": "MIT", | ||
| "author": "KNOWLEDGECODE", | ||
| "type": "module", | ||
| "main": "./dist/index.js", | ||
| "types": "./dist/index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/index.d.ts", | ||
| "import": "./dist/index.js" | ||
| } | ||
| }, | ||
| "files": [ | ||
| "LICENSE", | ||
| "README.md", | ||
| "dist/**/*" | ||
| ], | ||
| "sideEffects": false, | ||
| "scripts": { | ||
| "build": "rollup -c", | ||
| "lint": "eslint", | ||
| "test": "vitest run" | ||
| }, | ||
| "devDependencies": { | ||
| "@rollup/plugin-terser": "^0.3.0", | ||
| "chai": "^4.3.7", | ||
| "karma": "^6.4.1", | ||
| "karma-chai": "^0.1.0", | ||
| "karma-chrome-launcher": "^3.1.1", | ||
| "karma-mocha": "^2.0.1", | ||
| "karma-mocha-reporter": "^2.2.5", | ||
| "mocha": "^10.2.0", | ||
| "rollup": "^3.9.1", | ||
| "tsd": "^0.25.0" | ||
| "@eslint/js": "^9.39.2", | ||
| "@rollup/plugin-terser": "^0.4.4", | ||
| "@stylistic/eslint-plugin": "^5.6.1", | ||
| "@vitest/browser-playwright": "^4.0.16", | ||
| "eslint": "^9.39.2", | ||
| "playwright": "^1.57.0", | ||
| "rollup": "^4.54.0", | ||
| "rollup-plugin-dts": "^6.3.0", | ||
| "rollup-plugin-esbuild": "^6.2.1", | ||
| "rollup-plugin-license": "^3.6.0", | ||
| "typescript": "^5.9.3", | ||
| "typescript-eslint": "^8.50.1", | ||
| "vitest": "^4.0.16" | ||
| } | ||
| } |
+536
-119
| # Messenger | ||
| Messenger is a `Req/Rep` `Pub/Sub` library for iframes and workers. | ||
| A type-safe Request/Reply and Pub/Sub messaging library for cross-context communication in browsers, enabling seamless message exchange between the main window, iframes, and web workers. | ||
| ## Features | ||
| Allows messages to be exchanged between ... | ||
| - **Request/Reply Pattern**: Send requests and receive responses asynchronously | ||
| - **Pub/Sub Pattern**: Publish messages to multiple subscribers | ||
| - **Cross-Context Communication**: Works seamlessly between: | ||
| - Main window ↔ iframes | ||
| - Main window ↔ web workers | ||
| - iframe ↔ iframe | ||
| - Components within the same context | ||
| - **Type-Safe**: Built with TypeScript for excellent type inference | ||
| - **Promise-Based**: Modern async/await API | ||
| - **Secure**: Uses MessageChannel API for isolated communication | ||
| - the main window and one or more iframes / workers. | ||
| - multiple iframes. | ||
| - multiple components. | ||
| ## Installation | ||
| via npm: | ||
| ```shell | ||
@@ -23,20 +26,15 @@ npm i @knowledgecode/messenger | ||
| ```javascript | ||
| ### Node.js / Bundlers (Recommended) | ||
| ```typescript | ||
| import { MessengerClient, MessengerServer } from '@knowledgecode/messenger'; | ||
| ``` | ||
| ES Modules: | ||
| ### Browser (UMD - Legacy) | ||
| ```html | ||
| <script type="module"> | ||
| import { MessengerClient, MessengerServer } from '/path/to/esm/messenger.js'; | ||
| </script> | ||
| ``` | ||
| For legacy environments without ES module support: | ||
| Traditional: | ||
| ```html | ||
| <script src="/path/to/umd/messenger.js"></script> | ||
| <script src="./node_modules/@knowledgecode/messenger/dist/messenger.js"></script> | ||
| <script> | ||
| // It is provided with the global variable name "messenger". | ||
| const { MessengerClient, MessengerServer } = self.messenger; | ||
@@ -46,190 +44,609 @@ </script> | ||
| ## Simple Example | ||
| For workers in legacy environments: | ||
| main.js | ||
| ```javascript | ||
| import { MessengerClient } from '/path/to/esm/messenger.js'; | ||
| ```typescript | ||
| // worker.ts (legacy) | ||
| importScripts('./node_modules/@knowledgecode/messenger/dist/messenger.js'); | ||
| const { MessengerServer } = self.messenger; | ||
| ``` | ||
| ## Quick Start | ||
| > **Note**: The examples below are written in TypeScript. Build them to JavaScript files using TypeScript compiler or a bundler before running in the browser. | ||
| ### Example: Main Window ↔ Worker | ||
| #### main.ts (build to main.js) | ||
| ```typescript | ||
| import { MessengerClient } from '@knowledgecode/messenger'; | ||
| const messenger = new MessengerClient(); | ||
| const worker = new Worker('/path/to/worker.js'); | ||
| const worker = new Worker('./worker.js', { type: 'module' }); | ||
| (async () => { | ||
| await messenger.connect('example', worker); | ||
| // Connect to the worker's server named 'calculator' | ||
| await messenger.connect('calculator', worker); | ||
| const answer = await messenger.req('add', { x: 2, y: 3 }); | ||
| // Request/Reply: Send a request and wait for response | ||
| const result = await messenger.req<number>('add', { x: 2, y: 3 }); | ||
| console.log(result); // => 5 | ||
| console.log(answer); // => 5 | ||
| // Send: Fire and forget | ||
| messenger.send('close'); | ||
| messenger.disconnect(); | ||
| })(); | ||
| ``` | ||
| messenger.send('close'); | ||
| messenger.disconnect(); | ||
| #### worker.ts (build to worker.js) | ||
| ```typescript | ||
| import { MessengerServer } from '@knowledgecode/messenger'; | ||
| interface AddRequest { | ||
| x: number; | ||
| y: number; | ||
| } | ||
| const messenger = new MessengerServer('calculator', self); | ||
| // Bind handler for 'add' topic | ||
| messenger.bind<AddRequest>('add', (data) => { | ||
| if (!data) { | ||
| return 0; | ||
| } | ||
| return data.x + data.y; | ||
| }); | ||
| // Bind handler for 'close' topic | ||
| messenger.bind<void>('close', () => { | ||
| messenger.close(); | ||
| self.close(); | ||
| }); | ||
| ``` | ||
| ### Example: Main Window ↔ iframe | ||
| #### main.ts (build to main.js) | ||
| ```typescript | ||
| import { MessengerClient } from '@knowledgecode/messenger'; | ||
| interface StatusUpdate { | ||
| status: string; | ||
| timestamp: number; | ||
| } | ||
| interface User { | ||
| id: number; | ||
| name: string; | ||
| email: string; | ||
| } | ||
| const messenger = new MessengerClient(); | ||
| const iframe = document.querySelector('iframe'); | ||
| (async () => { | ||
| // Connect to iframe's server with targetOrigin for security | ||
| await messenger.connect('iframe-app', iframe!.contentWindow!, { | ||
| targetOrigin: 'https://example.com', // Use '*' only for development | ||
| timeout: 5000 | ||
| }); | ||
| // Subscribe to messages published from the iframe | ||
| messenger.subscribe<StatusUpdate>('status-update', (data) => { | ||
| if (data) { | ||
| console.log('Status:', data.status); | ||
| } | ||
| }); | ||
| // Send request to iframe | ||
| const userData = await messenger.req<User>('get-user', { id: 123 }); | ||
| console.log(userData); | ||
| })(); | ||
| ``` | ||
| worker.js | ||
| ```javascript | ||
| importScripts('/path/to/umd/messenger.js'); | ||
| #### iframe.ts (build to iframe.js) | ||
| const { MessengerServer } = self.messenger; | ||
| const messenger = new MessengerServer('example', self); | ||
| ```typescript | ||
| import { MessengerServer } from '@knowledgecode/messenger'; | ||
| messenger.bind('add', data => { | ||
| return data.x + data.y; | ||
| interface GetUserRequest { | ||
| id: number; | ||
| } | ||
| interface User { | ||
| id: number; | ||
| name: string; | ||
| email: string; | ||
| } | ||
| const messenger = new MessengerServer('iframe-app', self); | ||
| messenger.bind<GetUserRequest>('get-user', async (data) => { | ||
| if (!data) { | ||
| throw new Error('User ID is required'); | ||
| } | ||
| const response = await fetch(`/api/users/${data.id}`); | ||
| return await response.json() as User; | ||
| }); | ||
| messenger.bind('close', () => { | ||
| messenger.close(); | ||
| // Close this worker. | ||
| self.close(); | ||
| // Publish status updates to subscribers | ||
| setInterval(() => { | ||
| messenger.publish('status-update', { status: 'running', timestamp: Date.now() }); | ||
| }, 1000); | ||
| ``` | ||
| ### Example: Same-Context Communication (Component to Component) | ||
| You can use Messenger for communication between components within the same window or worker context. | ||
| #### data-service.ts (build to data-service.js) | ||
| ```typescript | ||
| import { MessengerServer } from '@knowledgecode/messenger'; | ||
| interface DataItem { | ||
| id: number; | ||
| value: string; | ||
| } | ||
| const messenger = new MessengerServer('data-service', self); | ||
| messenger.bind<void>('get-data', (): DataItem[] => { | ||
| return [ | ||
| { id: 1, value: 'Item 1' }, | ||
| { id: 2, value: 'Item 2' }, | ||
| { id: 3, value: 'Item 3' } | ||
| ]; | ||
| }); | ||
| ``` | ||
| ## MessengerClient API | ||
| #### app.ts (build to app.js) | ||
| ### `constructor()` | ||
| ```typescript | ||
| import { MessengerClient } from '@knowledgecode/messenger'; | ||
| ```javascript | ||
| interface DataItem { | ||
| id: number; | ||
| value: string; | ||
| } | ||
| const messenger = new MessengerClient(); | ||
| (async () => { | ||
| // Connect to the data service in the same context | ||
| await messenger.connect('data-service', self); | ||
| // Request data from the service | ||
| const items = await messenger.req<DataItem[]>('get-data'); | ||
| console.log('Received items:', items); | ||
| messenger.disconnect(); | ||
| })(); | ||
| ``` | ||
| ### `connect(name, [endpoint[, options]])` | ||
| ## API Reference | ||
| - {**string**} name - unique name of the MessengerServer to connect to | ||
| - {**Object**} [endpoint] - an object that actually executes the `postMessage()` | ||
| - {**Object**} [options] - connection options | ||
| ### MessengerClient | ||
| The `MessengerClient` must connect to a `MessengerServer` via `endpoint` before communication can begin. To identify the `MessengerServer` to connect to, pass the unique name of the `MessengerServer` as the first argument. The `endpoint` is the object that actually executes the `postMessage()`. If omitted, it is assumed that `self` is set. The `options` are connection options and members of this object are `targetOrigin` and `timeout` (msec). If the `timeout` is omitted, this method will wait forever for a successful connection. | ||
| Client for connecting to a MessengerServer and sending messages. | ||
| ```javascript | ||
| // To connect from the main window to a iframe. | ||
| const iframe = window.frames[0]; | ||
| #### `constructor()` | ||
| await messenger.connect('iframe', iframe, { targetOrigin: '*', timeout: 1000 }) | ||
| .catch(e => console.log(e)); | ||
| Creates a new MessengerClient instance. | ||
| ```typescript | ||
| const messenger = new MessengerClient(); | ||
| ``` | ||
| ```javascript | ||
| // To connect from the main window to a worker. | ||
| const worker = new Worker('/path/to/worker.js'); | ||
| #### `connect(name, endpoint, options)` | ||
| await messenger.connect('worker', worker, { timeout: 1000 }) | ||
| .catch(e => console.log(e)); | ||
| Establishes a connection to a MessengerServer. | ||
| **Parameters:** | ||
| - `name` (**string**): Unique name of the MessengerServer to connect to | ||
| - `endpoint` (**Window | Worker**, optional): Target context that has `postMessage()` method. Defaults to `self` | ||
| - `options` (**object**, optional): Connection options | ||
| - `targetOrigin` (**string**, optional): Target origin for security (iframe only). Defaults to `'*'`. For production, always specify the exact origin | ||
| - `timeout` (**number**, optional): Connection timeout in milliseconds. If omitted, waits indefinitely | ||
| **Returns:** `Promise<void>` - Resolves when connection is established | ||
| **Throws:** | ||
| - `Error` if endpoint doesn't have `postMessage()` method | ||
| - `Error` if connection times out | ||
| **Examples:** | ||
| ```typescript | ||
| // Connect to iframe with security and timeout | ||
| const iframe = document.querySelector('iframe'); | ||
| await messenger.connect('my-iframe', iframe!.contentWindow!, { | ||
| targetOrigin: 'https://trusted-domain.com', | ||
| timeout: 5000 | ||
| }); | ||
| ``` | ||
| ### `disconnect()` | ||
| ```typescript | ||
| // Connect to worker | ||
| const worker = new Worker('./worker.js', { type: 'module' }); | ||
| await messenger.connect('my-worker', worker, { timeout: 3000 }); | ||
| ``` | ||
| Disconnects from the server. | ||
| ```typescript | ||
| // Connect from within a worker to parent | ||
| await messenger.connect('main', self); | ||
| ``` | ||
| ```javascript | ||
| #### `disconnect()` | ||
| Disconnects from the server, clears all subscriptions, and cleans up resources. | ||
| ```typescript | ||
| messenger.disconnect(); | ||
| ``` | ||
| ### `send(topic[, data])` | ||
| #### `send(topic, data)` | ||
| - {**string**} topic - topic name | ||
| - {**Object**} [data] - an object to send | ||
| Sends a one-way message to a topic. Does not wait for a response. | ||
| Sends a message (some object) to a topic. This method does not wait for any reply. A `MessengerServer` can receive the message if it is bound to the same topic name in advance. | ||
| **Parameters:** | ||
| ```javascript | ||
| messenger.send('greeting', { hello: 'world' }); | ||
| - `topic` (**string**): Topic name | ||
| - `data` (**unknown**): Data to send | ||
| **Throws:** `Error` if not connected | ||
| ```typescript | ||
| messenger.send('log', { level: 'info', message: 'Task completed' }); | ||
| ``` | ||
| ### `req(topic[, data[, timeout]])` | ||
| #### `req<T>(topic, data, timeout)` | ||
| - {**string**} topic - topic name | ||
| - {**Object**} [data] - an object to send | ||
| - {**number**} [timeout] - timeout (msec) | ||
| Sends a request to a topic and waits for a response. | ||
| Sends a message (some object) to a topic. This method waits for some reply unlike `send()`. If `timeout` (msec) is omitted, this method waits forever for some reply. | ||
| **Type Parameters:** | ||
| ```javascript | ||
| const answer = await messenger.req('add', { x: 2, y: 3 }) | ||
| - `T` (optional): The expected response type. Defaults to `unknown` | ||
| console.log(answer); | ||
| **Parameters:** | ||
| - `topic` (**string**): Topic name | ||
| - `data` (**unknown**, optional): Data to send | ||
| - `timeout` (**number**, optional): Request timeout in milliseconds. If omitted, waits indefinitely | ||
| **Returns:** `Promise<T>` - Resolves with the response data of type `T` | ||
| **Throws:** | ||
| - `Error` if not connected | ||
| - `Error` if request times out | ||
| - `Error` if the topic is not bound on the server | ||
| **Examples:** | ||
| ```typescript | ||
| // Simple request with type inference | ||
| const result = await messenger.req<number>('calculate', { operation: 'add', values: [1, 2, 3] }); | ||
| ``` | ||
| ```javascript | ||
| await messenger.req('add', { x: 2, y: 3 }, 5000) | ||
| .catch(e => console.log(e)); // Catch timeout error. | ||
| ```typescript | ||
| // Request with timeout and type safety | ||
| interface DataResponse { | ||
| id: number; | ||
| value: string; | ||
| } | ||
| try { | ||
| const data = await messenger.req<DataResponse>('fetch-data', { id: 123 }, 5000); | ||
| console.log(data.value); // TypeScript knows about the 'value' property | ||
| } catch (error) { | ||
| console.error('Request failed:', (error as Error).message); | ||
| } | ||
| ``` | ||
| ### `subscribe(topic, listener)` | ||
| #### `subscribe<T>(topic, listener)` | ||
| - {**string**} topic - topic name | ||
| - {**Function**} listener - a listener to receive published messages | ||
| Subscribes to messages published on a topic. | ||
| Subscribes to messages on a topic. | ||
| **Type Parameters:** | ||
| ```javascript | ||
| messenger.subscribe('news', data => console.log(data)); | ||
| - `T` (optional): The expected message data type. Defaults to `unknown` | ||
| **Parameters:** | ||
| - `topic` (**string**): Topic name | ||
| - `listener` (**function**): Callback function invoked when messages are published | ||
| - Signature: `(data?: T) => void` | ||
| **Throws:** `Error` if not connected | ||
| ```typescript | ||
| interface Notification { | ||
| title: string; | ||
| message: string; | ||
| timestamp: number; | ||
| } | ||
| messenger.subscribe<Notification>('notifications', (data) => { | ||
| if (data) { | ||
| console.log('Notification received:', data.title); | ||
| } | ||
| }); | ||
| ``` | ||
| ### `unsubscribe(topic[, listener])` | ||
| #### `unsubscribe(topic, listener)` | ||
| Unsubscribes to messages on a topic. If listener is omitted, all listeners for the topic are cleared. | ||
| Unsubscribes from a topic. | ||
| - {**string**} topic - topic name | ||
| - {**Function**} [listener] - a listener to receive published messages | ||
| **Parameters:** | ||
| ```javascript | ||
| const listener = data => console.log(data); | ||
| messenger.subscribe('news', listener); | ||
| - `topic` (**string**, optional): Topic name. If omitted, clears all subscriptions | ||
| - `listener` (**function**, optional): Specific listener to remove. If omitted, removes all listeners for the topic | ||
| messenger.unsubscribe('news', listener); | ||
| ```typescript | ||
| // Remove specific listener | ||
| interface UpdateData { | ||
| version: string; | ||
| } | ||
| const listener = (data?: UpdateData) => { | ||
| if (data) { | ||
| console.log(data.version); | ||
| } | ||
| }; | ||
| messenger.subscribe<UpdateData>('updates', listener); | ||
| messenger.unsubscribe('updates', listener); | ||
| // Remove all listeners for a topic | ||
| messenger.unsubscribe('updates'); | ||
| // Remove all subscriptions | ||
| messenger.unsubscribe(); | ||
| ``` | ||
| ## MessengerServer API | ||
| --- | ||
| ### `constructor(name, [endpoint])` | ||
| ### MessengerServer | ||
| - {**string**} name - unique name of the MessengerServer | ||
| - {**Object**} [endpoint] - an object that actually executes the `postMessage()` | ||
| Server for accepting client connections and handling messages. | ||
| ```javascript | ||
| const messenger = new MessengerServer('server', self); | ||
| #### `constructor(name, endpoint)` | ||
| Creates a new MessengerServer instance. | ||
| **Parameters:** | ||
| - `name` (**string**): Unique name for this server. Clients use this name to connect | ||
| - `endpoint` (**Window | Worker**, optional): Context to listen on. Defaults to `self` | ||
| ```typescript | ||
| // In a worker | ||
| const messenger = new MessengerServer('my-worker', self); | ||
| // In an iframe | ||
| const messenger = new MessengerServer('my-iframe', self); | ||
| // In main window (listening for messages from a specific worker) | ||
| const worker = new Worker('./worker.js', { type: 'module' }); | ||
| const messenger = new MessengerServer('worker-listener', worker); | ||
| ``` | ||
| The `name` is a unique name by which clients identify this MessengerServer. The `endpoint` is the object that actually executes the `postMessage()`. If omitted, it is assumed that `self` is set. | ||
| #### `bind<T>(topic, listener)` | ||
| ### `bind(topic, listener)` | ||
| Binds a handler to a topic for receiving messages. | ||
| - {**string**} topic - topic name | ||
| - {**Function**} listener - a listener to receive messages | ||
| **Type Parameters:** | ||
| Binds a listener to listen for messages on a topic. The topic names must be unique, no other listener than the first can bind on the same topic name. This method returns `true` or `false` as binding result. | ||
| - `T` (optional): The expected message data type. Defaults to `unknown` | ||
| ```javascript | ||
| messenger.bind('greeting', data => console.log(data)); | ||
| **Parameters:** | ||
| messenger.bind('add', data => { | ||
| // Reply to client. | ||
| return data.x + data.y; | ||
| - `topic` (**string**): Topic name (must be unique per server) | ||
| - `listener` (**function**): Handler function | ||
| - Signature: `(data?: T) => unknown` | ||
| - For `send()` messages: return value is ignored | ||
| - For `req()` messages: return value (or resolved Promise value) is sent back to client | ||
| **Returns:** `boolean` - `true` if bound successfully, `false` if topic already bound | ||
| **Examples:** | ||
| ```typescript | ||
| // Handle one-way messages | ||
| interface LogMessage { | ||
| level: string; | ||
| message: string; | ||
| } | ||
| messenger.bind<LogMessage>('log', (data) => { | ||
| if (data) { | ||
| console.log(`[${data.level}] ${data.message}`); | ||
| } | ||
| }); | ||
| // Handle requests (synchronous) | ||
| interface AddRequest { | ||
| x: number; | ||
| y: number; | ||
| } | ||
| messenger.bind<AddRequest>('add', (data) => { | ||
| if (!data) { | ||
| return 0; | ||
| } | ||
| return data.x + data.y; | ||
| }); | ||
| // Handle requests (asynchronous) | ||
| interface FetchUserRequest { | ||
| id: number; | ||
| } | ||
| interface User { | ||
| id: number; | ||
| name: string; | ||
| } | ||
| messenger.bind<FetchUserRequest>('fetch-user', async (data) => { | ||
| if (!data) { | ||
| throw new Error('User ID is required'); | ||
| } | ||
| const response = await fetch(`/api/users/${data.id}`); | ||
| return await response.json() as User; | ||
| }); | ||
| // Check binding result | ||
| if (!messenger.bind<void>('duplicate-topic', () => {})) { | ||
| console.error('Topic already bound'); | ||
| } | ||
| ``` | ||
| ### `publish(topic, data)` | ||
| #### `unbind(topic)` | ||
| - {**string**} topic - topic name | ||
| - {**Object**} data - an object to publish | ||
| Removes the handler for a topic. | ||
| Publish a message (some object) to all subscribers on a topic. This method does not wait for reply from the subscribers, and also does not fail even there are no subscribers at all. | ||
| **Parameters:** | ||
| ```javascript | ||
| messenger.publish('notification', 'The process completed successfully.'); | ||
| - `topic` (**string**): Topic name | ||
| ```typescript | ||
| messenger.unbind('old-topic'); | ||
| ``` | ||
| ### `close()` | ||
| #### `publish(topic, data)` | ||
| Closes all connections and shuts down the server. | ||
| Publishes a message to all subscribed clients on a topic. | ||
| ```javascript | ||
| **Parameters:** | ||
| - `topic` (**string**): Topic name | ||
| - `data` (**unknown**, optional): Data to publish | ||
| This method does not wait for responses and succeeds even if there are no subscribers. | ||
| ```typescript | ||
| // Notify all subscribers | ||
| messenger.publish('status-change', { status: 'ready', timestamp: Date.now() }); | ||
| // Broadcast to all clients | ||
| setInterval(() => { | ||
| messenger.publish('heartbeat', { timestamp: Date.now() }); | ||
| }, 1000); | ||
| ``` | ||
| #### `close()` | ||
| Closes all client connections, removes all handlers and subscriptions, and shuts down the server. | ||
| ```typescript | ||
| messenger.close(); | ||
| ``` | ||
| ## Browser support | ||
| ## Security Considerations | ||
| Chrome, Safari, Firefox, Edge | ||
| When connecting to iframes from different origins, always specify the exact `targetOrigin` instead of using `'*'`: | ||
| ```typescript | ||
| // ❌ Insecure - allows any origin | ||
| await messenger.connect('iframe', iframe!.contentWindow!, { targetOrigin: '*' }); | ||
| // ✅ Secure - restricts to specific origin | ||
| await messenger.connect('iframe', iframe!.contentWindow!, { | ||
| targetOrigin: 'https://trusted-domain.com' | ||
| }); | ||
| ``` | ||
| ## TypeScript | ||
| This library is written in TypeScript and provides full type safety with generic support. | ||
| ### Type-safe Requests | ||
| ```typescript | ||
| import { MessengerClient } from '@knowledgecode/messenger'; | ||
| interface User { | ||
| id: number; | ||
| name: string; | ||
| } | ||
| const messenger = new MessengerClient(); | ||
| // Type-safe request - result is typed as User | ||
| const user = await messenger.req<User>('get-user', { id: 123 }); | ||
| console.log(user.name); // TypeScript knows user has a name property | ||
| ``` | ||
| ### Type-safe Subscriptions | ||
| The listener receives `data` as `T | undefined` because messages may be published without data. | ||
| ```typescript | ||
| interface StatusUpdate { | ||
| status: 'online' | 'offline'; | ||
| timestamp: number; | ||
| } | ||
| // Type-safe subscribe - data parameter is StatusUpdate | undefined | ||
| messenger.subscribe<StatusUpdate>('status-change', (data) => { | ||
| if (data) { | ||
| console.log(`Status: ${data.status} at ${data.timestamp}`); | ||
| } | ||
| }); | ||
| ``` | ||
| ### Type-safe Handlers | ||
| Handlers receive `data` as `T | undefined` because clients may send requests or messages without data. | ||
| ```typescript | ||
| interface CalculateRequest { | ||
| operation: 'add' | 'subtract' | 'multiply' | 'divide'; | ||
| a: number; | ||
| b: number; | ||
| } | ||
| interface CalculateResponse { | ||
| result: number; | ||
| } | ||
| const messenger = new MessengerServer('calculator', self); | ||
| // Type-safe handler - data parameter is CalculateRequest | undefined | ||
| messenger.bind<CalculateRequest>('calculate', (data) => { | ||
| if (!data) { | ||
| return { result: 0 }; | ||
| } | ||
| switch (data.operation) { | ||
| case 'add': | ||
| return { result: data.a + data.b }; | ||
| case 'subtract': | ||
| return { result: data.a - data.b }; | ||
| case 'multiply': | ||
| return { result: data.a * data.b }; | ||
| case 'divide': | ||
| return { result: data.a / data.b }; | ||
| } | ||
| }); | ||
| // Client side - response is typed as CalculateResponse | ||
| const client = new MessengerClient(); | ||
| await client.connect('calculator', self); | ||
| const response = await client.req<CalculateResponse>('calculate', { | ||
| operation: 'add', | ||
| a: 5, | ||
| b: 3 | ||
| }); | ||
| console.log(response.result); // TypeScript knows about result property | ||
| ``` | ||
| ## License | ||
| MIT |
| class s{constructor(){this._id=void 0,this._port=void 0,this._reps=new Map,this._subscribers=new Map}_listener(s){const{method:e}=s.data||{},{id:t,topic:i,data:r,error:o}=(s.data||{}).payload||{};switch(e){case"reply":const s=this._reps.get(t);s&&(s.timerId&&clearTimeout(s.timerId),o?s.reject(o):s.resolve(r),this._reps.delete(t));break;case"publish":const e=this._subscribers.get(i);if(e)for(const s of e.values())s(r)}}_postMessage(s,e,t,i){this._port.postMessage({method:s,payload:{client:this._id,id:e,topic:t,data:i}})}connect(s,e=self,t={}){return this._port?Promise.resolve():"postMessage"in e?new Promise(((i,r)=>{const{port1:o,port2:n}=new MessageChannel,{targetOrigin:a,timeout:c}=t,h=c>0?setTimeout((()=>{this._port.close(),this._port=void 0,r(new Error("Connection timed out."))}),c):0;this._port=o,this._port.onmessage=s=>{const{method:e}=s.data||{},{client:t}=(s.data||{}).payload||{};"ack"===e&&(this._id=t,this._port.onmessage=s=>this._listener(s),h&&clearTimeout(h),i())},e.postMessage({name:s,method:"connect"},{targetOrigin:a||"/",transfer:[n]})})):Promise.reject(new Error("The endpoint has no postMessage method."))}disconnect(){this._port&&(this._postMessage("disconnect"),this._port.close(),this._port=void 0),this._reps.clear(),this.unsubscribe()}send(s,e){if(!this._port)throw new Error("No connected.");this._postMessage("send","",s,e)}req(s,e,t=0){return new Promise(((i,r)=>{if(!this._port)return void r(new Error("No connected."));const o=Math.random().toString(36).slice(2),n=t>0?setTimeout((()=>{const s=this._reps.get(o);s&&(s.reject(new Error("Request timed out.")),this._reps.delete(o))}),t):0;this._reps.set(o,{resolve:i,reject:r,timerId:n}),this._postMessage("request",o,s,e)}))}subscribe(s,e){if(!this._port)throw new Error("No connected.");const t=this._subscribers.get(s)||new Set;t.add(e),this._subscribers.set(s,t),this._postMessage("subscribe","",s)}unsubscribe(s,e){if(e){const t=this._subscribers.get(s);t&&t.delete(e)}else s?this._subscribers.delete(s):this._subscribers.clear()}}class e{constructor(s,e=self){this._clients=new Map,this._listeners=new Map,this._subscribers=new Map,this._endpoint=e,this._accept=e=>{const{name:t,method:i}=e.data||{},[r]=e.ports||[];if(s!==t||"connect"!==i||!r)return;e.stopImmediatePropagation();const o=Math.random().toString(36).slice(2);r.onmessage=s=>this._listener(s),this._clients.set(o,r),r.postMessage({method:"ack",payload:{client:o}})},this._endpoint.addEventListener("message",this._accept)}_listener(s){const{method:e}=s.data||{},{client:t,id:i,topic:r,data:o}=(s.data||{}).payload||{};switch(e){case"send":this._listeners.has(r)&&this._listeners.get(r)(o);break;case"request":this._listeners.has(r)?(n=this._listeners.get(r)(o),n instanceof Promise?n:Promise.resolve(n)).then((s=>this._postMessage(t,"reply",i,r,s))).catch((s=>this._postMessage(t,"reply",i,r,void 0,s))):this._postMessage(t,"reply",i,r,void 0,"Topic is not bound.");break;case"subscribe":const s=this._subscribers.get(r)||new Set;s.add(t),this._subscribers.set(r,s);break;case"disconnect":this._clients.has(t)&&(this._clients.get(t).close(),this._clients.delete(t))}var n}_postMessage(s,e,t,i,r,o){const n=this._clients.get(s);n&&n.postMessage({method:e,payload:{id:t,topic:i,data:r,error:o}})}bind(s,e){return!this._listeners.has(s)&&(this._listeners.set(s,e),!0)}unbind(s){this._listeners.delete(s)}publish(s,e){const t=this._subscribers.get(s);if(t)for(const i of t.values())this._postMessage(i,"publish","",s,e)}close(){for(const s of this._clients.values())s.close();this._clients.clear(),this._listeners.clear(),this._subscribers.clear(),this._endpoint.removeEventListener("message",this._accept),this._endpoint=void 0}}export{s as MessengerClient,e as MessengerServer}; |
| class s{constructor(){this._id=void 0,this._port=void 0,this._reps=new Map,this._subscribers=new Map}_listener(s){const{method:e}=s.data||{},{id:t,topic:i,data:r,error:o}=(s.data||{}).payload||{};switch(e){case"reply":const s=this._reps.get(t);s&&(s.timerId&&clearTimeout(s.timerId),o?s.reject(o):s.resolve(r),this._reps.delete(t));break;case"publish":const e=this._subscribers.get(i);if(e)for(const s of e.values())s(r)}}_postMessage(s,e,t,i){this._port.postMessage({method:s,payload:{client:this._id,id:e,topic:t,data:i}})}connect(s,e=self,t={}){return this._port?Promise.resolve():"postMessage"in e?new Promise(((i,r)=>{const{port1:o,port2:n}=new MessageChannel,{targetOrigin:a,timeout:c}=t,h=c>0?setTimeout((()=>{this._port.close(),this._port=void 0,r(new Error("Connection timed out."))}),c):0;this._port=o,this._port.onmessage=s=>{const{method:e}=s.data||{},{client:t}=(s.data||{}).payload||{};"ack"===e&&(this._id=t,this._port.onmessage=s=>this._listener(s),h&&clearTimeout(h),i())},e.postMessage({name:s,method:"connect"},{targetOrigin:a||"/",transfer:[n]})})):Promise.reject(new Error("The endpoint has no postMessage method."))}disconnect(){this._port&&(this._postMessage("disconnect"),this._port.close(),this._port=void 0),this._reps.clear(),this.unsubscribe()}send(s,e){if(!this._port)throw new Error("No connected.");this._postMessage("send","",s,e)}req(s,e,t=0){return new Promise(((i,r)=>{if(!this._port)return void r(new Error("No connected."));const o=Math.random().toString(36).slice(2),n=t>0?setTimeout((()=>{const s=this._reps.get(o);s&&(s.reject(new Error("Request timed out.")),this._reps.delete(o))}),t):0;this._reps.set(o,{resolve:i,reject:r,timerId:n}),this._postMessage("request",o,s,e)}))}subscribe(s,e){if(!this._port)throw new Error("No connected.");const t=this._subscribers.get(s)||new Set;t.add(e),this._subscribers.set(s,t),this._postMessage("subscribe","",s)}unsubscribe(s,e){if(e){const t=this._subscribers.get(s);t&&t.delete(e)}else s?this._subscribers.delete(s):this._subscribers.clear()}}class e{constructor(s,e=self){this._clients=new Map,this._listeners=new Map,this._subscribers=new Map,this._endpoint=e,this._accept=e=>{const{name:t,method:i}=e.data||{},[r]=e.ports||[];if(s!==t||"connect"!==i||!r)return;e.stopImmediatePropagation();const o=Math.random().toString(36).slice(2);r.onmessage=s=>this._listener(s),this._clients.set(o,r),r.postMessage({method:"ack",payload:{client:o}})},this._endpoint.addEventListener("message",this._accept)}_listener(s){const{method:e}=s.data||{},{client:t,id:i,topic:r,data:o}=(s.data||{}).payload||{};switch(e){case"send":this._listeners.has(r)&&this._listeners.get(r)(o);break;case"request":this._listeners.has(r)?(n=this._listeners.get(r)(o),n instanceof Promise?n:Promise.resolve(n)).then((s=>this._postMessage(t,"reply",i,r,s))).catch((s=>this._postMessage(t,"reply",i,r,void 0,s))):this._postMessage(t,"reply",i,r,void 0,"Topic is not bound.");break;case"subscribe":const s=this._subscribers.get(r)||new Set;s.add(t),this._subscribers.set(r,s);break;case"disconnect":this._clients.has(t)&&(this._clients.get(t).close(),this._clients.delete(t))}var n}_postMessage(s,e,t,i,r,o){const n=this._clients.get(s);n&&n.postMessage({method:e,payload:{id:t,topic:i,data:r,error:o}})}bind(s,e){return!this._listeners.has(s)&&(this._listeners.set(s,e),!0)}unbind(s){this._listeners.delete(s)}publish(s,e){const t=this._subscribers.get(s);if(t)for(const i of t.values())this._postMessage(i,"publish","",s,e)}close(){for(const s of this._clients.values())s.close();this._clients.clear(),this._listeners.clear(),this._subscribers.clear(),this._endpoint.removeEventListener("message",this._accept),this._endpoint=void 0}}export{s as MessengerClient,e as MessengerServer}; |
| !function(e,s){"object"==typeof exports&&"undefined"!=typeof module?s(exports):"function"==typeof define&&define.amd?define(["exports"],s):s((e="undefined"!=typeof globalThis?globalThis:e||self).messenger={})}(this,(function(e){"use strict";e.MessengerClient=class{constructor(){this._id=void 0,this._port=void 0,this._reps=new Map,this._subscribers=new Map}_listener(e){const{method:s}=e.data||{},{id:t,topic:i,data:o,error:r}=(e.data||{}).payload||{};switch(s){case"reply":const e=this._reps.get(t);e&&(e.timerId&&clearTimeout(e.timerId),r?e.reject(r):e.resolve(o),this._reps.delete(t));break;case"publish":const s=this._subscribers.get(i);if(s)for(const e of s.values())e(o)}}_postMessage(e,s,t,i){this._port.postMessage({method:e,payload:{client:this._id,id:s,topic:t,data:i}})}connect(e,s=self,t={}){return this._port?Promise.resolve():"postMessage"in s?new Promise(((i,o)=>{const{port1:r,port2:n}=new MessageChannel,{targetOrigin:c,timeout:a}=t,h=a>0?setTimeout((()=>{this._port.close(),this._port=void 0,o(new Error("Connection timed out."))}),a):0;this._port=r,this._port.onmessage=e=>{const{method:s}=e.data||{},{client:t}=(e.data||{}).payload||{};"ack"===s&&(this._id=t,this._port.onmessage=e=>this._listener(e),h&&clearTimeout(h),i())},s.postMessage({name:e,method:"connect"},{targetOrigin:c||"/",transfer:[n]})})):Promise.reject(new Error("The endpoint has no postMessage method."))}disconnect(){this._port&&(this._postMessage("disconnect"),this._port.close(),this._port=void 0),this._reps.clear(),this.unsubscribe()}send(e,s){if(!this._port)throw new Error("No connected.");this._postMessage("send","",e,s)}req(e,s,t=0){return new Promise(((i,o)=>{if(!this._port)return void o(new Error("No connected."));const r=Math.random().toString(36).slice(2),n=t>0?setTimeout((()=>{const e=this._reps.get(r);e&&(e.reject(new Error("Request timed out.")),this._reps.delete(r))}),t):0;this._reps.set(r,{resolve:i,reject:o,timerId:n}),this._postMessage("request",r,e,s)}))}subscribe(e,s){if(!this._port)throw new Error("No connected.");const t=this._subscribers.get(e)||new Set;t.add(s),this._subscribers.set(e,t),this._postMessage("subscribe","",e)}unsubscribe(e,s){if(s){const t=this._subscribers.get(e);t&&t.delete(s)}else e?this._subscribers.delete(e):this._subscribers.clear()}},e.MessengerServer=class{constructor(e,s=self){this._clients=new Map,this._listeners=new Map,this._subscribers=new Map,this._endpoint=s,this._accept=s=>{const{name:t,method:i}=s.data||{},[o]=s.ports||[];if(e!==t||"connect"!==i||!o)return;s.stopImmediatePropagation();const r=Math.random().toString(36).slice(2);o.onmessage=e=>this._listener(e),this._clients.set(r,o),o.postMessage({method:"ack",payload:{client:r}})},this._endpoint.addEventListener("message",this._accept)}_listener(e){const{method:s}=e.data||{},{client:t,id:i,topic:o,data:r}=(e.data||{}).payload||{};switch(s){case"send":this._listeners.has(o)&&this._listeners.get(o)(r);break;case"request":this._listeners.has(o)?(n=this._listeners.get(o)(r),n instanceof Promise?n:Promise.resolve(n)).then((e=>this._postMessage(t,"reply",i,o,e))).catch((e=>this._postMessage(t,"reply",i,o,void 0,e))):this._postMessage(t,"reply",i,o,void 0,"Topic is not bound.");break;case"subscribe":const e=this._subscribers.get(o)||new Set;e.add(t),this._subscribers.set(o,e);break;case"disconnect":this._clients.has(t)&&(this._clients.get(t).close(),this._clients.delete(t))}var n}_postMessage(e,s,t,i,o,r){const n=this._clients.get(e);n&&n.postMessage({method:s,payload:{id:t,topic:i,data:o,error:r}})}bind(e,s){return!this._listeners.has(e)&&(this._listeners.set(e,s),!0)}unbind(e){this._listeners.delete(e)}publish(e,s){const t=this._subscribers.get(e);if(t)for(const i of t.values())this._postMessage(i,"publish","",e,s)}close(){for(const e of this._clients.values())e.close();this._clients.clear(),this._listeners.clear(),this._subscribers.clear(),this._endpoint.removeEventListener("message",this._accept),this._endpoint=void 0}}})); |
| export class MessengerClient { | ||
| constructor(); | ||
| connect(name: string, endpoint?: any, options?: { targetOrigin?: string, timeout?: number }): Promise<void>; | ||
| disconnect(): void; | ||
| send(topic: string, data?: any): void; | ||
| req(topic: string, data?: any, timeout?: number): Promise<unknown>; | ||
| subscribe(topic: string, listener: (data: unknown) => void): void; | ||
| unsubscribe(topic?: string, listener?: (data: unknown) => void): void; | ||
| } | ||
| export class MessengerServer { | ||
| constructor(name: string, endpoint?: any); | ||
| bind(topic: string, listener: (data: unknown) => void): boolean; | ||
| unbind(topic: string): void; | ||
| publish(topic: string, data?: any): void; | ||
| close(): void; | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
29692
46.35%138
122.58%3
-25%651
178.21%Yes
NaN13
30%6
-14.29%