{"id":22296434,"url":"https://github.com/knowledgecode/messenger","last_synced_at":"2026-04-19T09:03:28.571Z","repository":{"id":56899673,"uuid":"202886737","full_name":"knowledgecode/messenger","owner":"knowledgecode","description":"Type-safe Request/Reply and Pub/Sub messaging library for browser applications","archived":false,"fork":false,"pushed_at":"2026-03-30T09:28:36.000Z","size":335,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-30T11:33:49.743Z","etag":null,"topics":["browser","communication","components","iframe","message-channel","messaging","pub-sub","pubsub","request-reply","type-safe","typescript","worker"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/knowledgecode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-08-17T13:51:01.000Z","updated_at":"2026-03-30T09:28:34.000Z","dependencies_parsed_at":"2023-02-08T05:31:20.514Z","dependency_job_id":null,"html_url":"https://github.com/knowledgecode/messenger","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/knowledgecode/messenger","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knowledgecode%2Fmessenger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knowledgecode%2Fmessenger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knowledgecode%2Fmessenger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knowledgecode%2Fmessenger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/knowledgecode","download_url":"https://codeload.github.com/knowledgecode/messenger/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knowledgecode%2Fmessenger/sbom","scorecard":{"id":564778,"data":{"date":"2025-08-11","repo":{"name":"github.com/knowledgecode/messenger","commit":"f7e56001f4cf79b128742006306e3a1ee73add39"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Code-Review","score":0,"reason":"Found 0/21 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 10 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"18 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-q9mw-68c2-j6m5","Warn: Project is vulnerable to: GHSA-jchw-25xp-jwwc","Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp","Warn: Project is vulnerable to: GHSA-4q6p-r6v2-jvc5","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-76p7-773f-r4q5","Warn: Project is vulnerable to: GHSA-25hc-qcg6-38wj","Warn: Project is vulnerable to: GHSA-cqmj-92xf-r6r9","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-fhg7-m89q-25r3","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T14:40:53.485Z","repository_id":56899673,"created_at":"2025-08-20T14:40:53.485Z","updated_at":"2025-08-20T14:40:53.485Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32000742,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T20:23:30.271Z","status":"online","status_checked_at":"2026-04-19T02:00:07.110Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["browser","communication","components","iframe","message-channel","messaging","pub-sub","pubsub","request-reply","type-safe","typescript","worker"],"created_at":"2024-12-03T17:45:35.291Z","updated_at":"2026-04-19T09:03:28.559Z","avatar_url":"https://github.com/knowledgecode.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Messenger\n\nA 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.\n\n## Features\n\n- **Request/Reply Pattern**: Send requests and receive responses asynchronously\n- **Pub/Sub Pattern**: Publish messages to multiple subscribers\n- **Cross-Context Communication**: Works seamlessly between:\n  - Main window ↔ iframes\n  - Main window ↔ web workers\n  - iframe ↔ iframe\n  - Components within the same context\n- **Type-Safe**: Built with TypeScript for excellent type inference\n- **Promise-Based**: Modern async/await API\n- **Secure**: Uses MessageChannel API for isolated communication\n\n## Installation\n\n```shell\nnpm i @knowledgecode/messenger\n```\n\n## Usage\n\n### Node.js / Bundlers (Recommended)\n\n```typescript\nimport { MessengerClient, MessengerServer } from '@knowledgecode/messenger';\n```\n\n### Browser (UMD - Legacy)\n\nFor legacy environments without ES module support:\n\n```html\n\u003cscript src=\"./node_modules/@knowledgecode/messenger/dist/messenger.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  const { MessengerClient, MessengerServer } = self.messenger;\n\u003c/script\u003e\n```\n\nFor workers in legacy environments:\n\n```typescript\n// worker.ts (legacy)\nimportScripts('./node_modules/@knowledgecode/messenger/dist/messenger.js');\nconst { MessengerServer } = self.messenger;\n```\n\n## Quick Start\n\n\u003e **Note**: The examples below are written in TypeScript. Build them to JavaScript files using TypeScript compiler or a bundler before running in the browser.\n\n### Example: Main Window ↔ Worker\n\n#### main.ts (build to main.js)\n\n```typescript\nimport { MessengerClient } from '@knowledgecode/messenger';\n\nconst messenger = new MessengerClient();\nconst worker = new Worker('./worker.js', { type: 'module' });\n\n(async () =\u003e {\n  // Connect to the worker's server named 'calculator'\n  await messenger.connect('calculator', worker);\n\n  // Request/Reply: Send a request and wait for response\n  const result = await messenger.req\u003cnumber\u003e('add', { x: 2, y: 3 });\n  console.log(result); // =\u003e 5\n\n  // Send: Fire and forget\n  messenger.send('close');\n  messenger.disconnect();\n})();\n```\n\n#### worker.ts (build to worker.js)\n\n```typescript\nimport { MessengerServer } from '@knowledgecode/messenger';\n\ninterface AddRequest {\n  x: number;\n  y: number;\n}\n\nconst messenger = new MessengerServer('calculator', self);\n\n// Bind handler for 'add' topic\nmessenger.bind\u003cAddRequest\u003e('add', (data) =\u003e {\n  if (!data) {\n    return 0;\n  }\n  return data.x + data.y;\n});\n\n// Bind handler for 'close' topic\nmessenger.bind\u003cvoid\u003e('close', () =\u003e {\n  messenger.close();\n  self.close();\n});\n```\n\n### Example: Main Window ↔ iframe\n\n#### main.ts (build to main.js)\n\n```typescript\nimport { MessengerClient } from '@knowledgecode/messenger';\n\ninterface StatusUpdate {\n  status: string;\n  timestamp: number;\n}\n\ninterface User {\n  id: number;\n  name: string;\n  email: string;\n}\n\nconst messenger = new MessengerClient();\nconst iframe = document.querySelector('iframe');\n\n(async () =\u003e {\n  // Connect to iframe's server with targetOrigin for security\n  await messenger.connect('iframe-app', iframe!.contentWindow!, {\n    targetOrigin: 'https://example.com', // Use '*' only for development\n    timeout: 5000\n  });\n\n  // Subscribe to messages published from the iframe\n  messenger.subscribe\u003cStatusUpdate\u003e('status-update', (data) =\u003e {\n    if (data) {\n      console.log('Status:', data.status);\n    }\n  });\n\n  // Send request to iframe\n  const userData = await messenger.req\u003cUser\u003e('get-user', { id: 123 });\n  console.log(userData);\n})();\n```\n\n#### iframe.ts (build to iframe.js)\n\n```typescript\nimport { MessengerServer } from '@knowledgecode/messenger';\n\ninterface GetUserRequest {\n  id: number;\n}\n\ninterface User {\n  id: number;\n  name: string;\n  email: string;\n}\n\nconst messenger = new MessengerServer('iframe-app', self);\n\nmessenger.bind\u003cGetUserRequest\u003e('get-user', async (data) =\u003e {\n  if (!data) {\n    throw new Error('User ID is required');\n  }\n  const response = await fetch(`/api/users/${data.id}`);\n  return await response.json() as User;\n});\n\n// Publish status updates to subscribers\nsetInterval(() =\u003e {\n  messenger.publish('status-update', { status: 'running', timestamp: Date.now() });\n}, 1000);\n```\n\n### Example: Same-Context Communication (Component to Component)\n\nYou can use Messenger for communication between components within the same window or worker context.\n\n#### data-service.ts (build to data-service.js)\n\n```typescript\nimport { MessengerServer } from '@knowledgecode/messenger';\n\ninterface DataItem {\n  id: number;\n  value: string;\n}\n\nconst messenger = new MessengerServer('data-service', self);\n\nmessenger.bind\u003cvoid\u003e('get-data', (): DataItem[] =\u003e {\n  return [\n    { id: 1, value: 'Item 1' },\n    { id: 2, value: 'Item 2' },\n    { id: 3, value: 'Item 3' }\n  ];\n});\n```\n\n#### app.ts (build to app.js)\n\n```typescript\nimport { MessengerClient } from '@knowledgecode/messenger';\n\ninterface DataItem {\n  id: number;\n  value: string;\n}\n\nconst messenger = new MessengerClient();\n\n(async () =\u003e {\n  // Connect to the data service in the same context\n  await messenger.connect('data-service', self);\n\n  // Request data from the service\n  const items = await messenger.req\u003cDataItem[]\u003e('get-data');\n  console.log('Received items:', items);\n\n  messenger.disconnect();\n})();\n```\n\n## API Reference\n\n### MessengerClient\n\nClient for connecting to a MessengerServer and sending messages.\n\n#### `constructor()`\n\nCreates a new MessengerClient instance.\n\n```typescript\nconst messenger = new MessengerClient();\n```\n\n#### `connect(name, endpoint, options)`\n\nEstablishes a connection to a MessengerServer.\n\n**Parameters:**\n\n- `name` (**string**): Unique name of the MessengerServer to connect to\n- `endpoint` (**Window | Worker**, optional): Target context that has `postMessage()` method. Defaults to `self`\n- `options` (**object**, optional): Connection options\n  - `targetOrigin` (**string**, optional): Target origin for security (iframe only). Defaults to `'*'`. For production, always specify the exact origin\n  - `timeout` (**number**, optional): Connection timeout in milliseconds. If omitted, waits indefinitely\n\n**Returns:** `Promise\u003cvoid\u003e` - Resolves when connection is established\n\n**Throws:**\n\n- `Error` if endpoint doesn't have `postMessage()` method\n- `Error` if connection times out\n\n**Examples:**\n\n```typescript\n// Connect to iframe with security and timeout\nconst iframe = document.querySelector('iframe');\nawait messenger.connect('my-iframe', iframe!.contentWindow!, {\n  targetOrigin: 'https://trusted-domain.com',\n  timeout: 5000\n});\n```\n\n```typescript\n// Connect to worker\nconst worker = new Worker('./worker.js', { type: 'module' });\nawait messenger.connect('my-worker', worker, { timeout: 3000 });\n```\n\n```typescript\n// Connect from within a worker to parent\nawait messenger.connect('main', self);\n```\n\n#### `disconnect()`\n\nDisconnects from the server, clears all subscriptions, and cleans up resources.\n\n```typescript\nmessenger.disconnect();\n```\n\n#### `send(topic, data)`\n\nSends a one-way message to a topic. Does not wait for a response.\n\n**Parameters:**\n\n- `topic` (**string**): Topic name\n- `data` (**unknown**): Data to send\n\n**Throws:** `Error` if not connected\n\n```typescript\nmessenger.send('log', { level: 'info', message: 'Task completed' });\n```\n\n#### `req\u003cT\u003e(topic, data, timeout)`\n\nSends a request to a topic and waits for a response.\n\n**Type Parameters:**\n\n- `T` (optional): The expected response type. Defaults to `unknown`\n\n**Parameters:**\n\n- `topic` (**string**): Topic name\n- `data` (**unknown**, optional): Data to send\n- `timeout` (**number**, optional): Request timeout in milliseconds. If omitted, waits indefinitely\n\n**Returns:** `Promise\u003cT\u003e` - Resolves with the response data of type `T`\n\n**Throws:**\n\n- `Error` if not connected\n- `Error` if request times out\n- `Error` if the topic is not bound on the server\n\n**Examples:**\n\n```typescript\n// Simple request with type inference\nconst result = await messenger.req\u003cnumber\u003e('calculate', { operation: 'add', values: [1, 2, 3] });\n```\n\n```typescript\n// Request with timeout and type safety\ninterface DataResponse {\n  id: number;\n  value: string;\n}\n\ntry {\n  const data = await messenger.req\u003cDataResponse\u003e('fetch-data', { id: 123 }, 5000);\n  console.log(data.value); // TypeScript knows about the 'value' property\n} catch (error) {\n  console.error('Request failed:', (error as Error).message);\n}\n```\n\n#### `subscribe\u003cT\u003e(topic, listener)`\n\nSubscribes to messages published on a topic.\n\n**Type Parameters:**\n\n- `T` (optional): The expected message data type. Defaults to `unknown`\n\n**Parameters:**\n\n- `topic` (**string**): Topic name\n- `listener` (**function**): Callback function invoked when messages are published\n  - Signature: `(data?: T) =\u003e void`\n\n**Throws:** `Error` if not connected\n\n```typescript\ninterface Notification {\n  title: string;\n  message: string;\n  timestamp: number;\n}\n\nmessenger.subscribe\u003cNotification\u003e('notifications', (data) =\u003e {\n  if (data) {\n    console.log('Notification received:', data.title);\n  }\n});\n```\n\n#### `unsubscribe(topic, listener)`\n\nUnsubscribes from a topic.\n\n**Parameters:**\n\n- `topic` (**string**, optional): Topic name. If omitted, clears all subscriptions\n- `listener` (**function**, optional): Specific listener to remove. If omitted, removes all listeners for the topic\n\n```typescript\n// Remove specific listener\ninterface UpdateData {\n  version: string;\n}\n\nconst listener = (data?: UpdateData) =\u003e {\n  if (data) {\n    console.log(data.version);\n  }\n};\nmessenger.subscribe\u003cUpdateData\u003e('updates', listener);\nmessenger.unsubscribe('updates', listener);\n\n// Remove all listeners for a topic\nmessenger.unsubscribe('updates');\n\n// Remove all subscriptions\nmessenger.unsubscribe();\n```\n\n---\n\n### MessengerServer\n\nServer for accepting client connections and handling messages.\n\n#### `constructor(name, endpoint)`\n\nCreates a new MessengerServer instance.\n\n**Parameters:**\n\n- `name` (**string**): Unique name for this server. Clients use this name to connect\n- `endpoint` (**Window | Worker**, optional): Context to listen on. Defaults to `self`\n\n```typescript\n// In a worker\nconst messenger = new MessengerServer('my-worker', self);\n\n// In an iframe\nconst messenger = new MessengerServer('my-iframe', self);\n\n// In main window (listening for messages from a specific worker)\nconst worker = new Worker('./worker.js', { type: 'module' });\nconst messenger = new MessengerServer('worker-listener', worker);\n```\n\n#### `bind\u003cT\u003e(topic, listener)`\n\nBinds a handler to a topic for receiving messages.\n\n**Type Parameters:**\n\n- `T` (optional): The expected message data type. Defaults to `unknown`\n\n**Parameters:**\n\n- `topic` (**string**): Topic name (must be unique per server)\n- `listener` (**function**): Handler function\n  - Signature: `(data?: T) =\u003e unknown`\n  - For `send()` messages: return value is ignored\n  - For `req()` messages: return value (or resolved Promise value) is sent back to client\n\n**Returns:** `boolean` - `true` if bound successfully, `false` if topic already bound\n\n**Examples:**\n\n```typescript\n// Handle one-way messages\ninterface LogMessage {\n  level: string;\n  message: string;\n}\n\nmessenger.bind\u003cLogMessage\u003e('log', (data) =\u003e {\n  if (data) {\n    console.log(`[${data.level}] ${data.message}`);\n  }\n});\n\n// Handle requests (synchronous)\ninterface AddRequest {\n  x: number;\n  y: number;\n}\n\nmessenger.bind\u003cAddRequest\u003e('add', (data) =\u003e {\n  if (!data) {\n    return 0;\n  }\n  return data.x + data.y;\n});\n\n// Handle requests (asynchronous)\ninterface FetchUserRequest {\n  id: number;\n}\n\ninterface User {\n  id: number;\n  name: string;\n}\n\nmessenger.bind\u003cFetchUserRequest\u003e('fetch-user', async (data) =\u003e {\n  if (!data) {\n    throw new Error('User ID is required');\n  }\n  const response = await fetch(`/api/users/${data.id}`);\n  return await response.json() as User;\n});\n\n// Check binding result\nif (!messenger.bind\u003cvoid\u003e('duplicate-topic', () =\u003e {})) {\n  console.error('Topic already bound');\n}\n```\n\n#### `unbind(topic)`\n\nRemoves the handler for a topic.\n\n**Parameters:**\n\n- `topic` (**string**): Topic name\n\n```typescript\nmessenger.unbind('old-topic');\n```\n\n#### `publish(topic, data)`\n\nPublishes a message to all subscribed clients on a topic.\n\n**Parameters:**\n\n- `topic` (**string**): Topic name\n- `data` (**unknown**, optional): Data to publish\n\nThis method does not wait for responses and succeeds even if there are no subscribers.\n\n```typescript\n// Notify all subscribers\nmessenger.publish('status-change', { status: 'ready', timestamp: Date.now() });\n\n// Broadcast to all clients\nsetInterval(() =\u003e {\n  messenger.publish('heartbeat', { timestamp: Date.now() });\n}, 1000);\n```\n\n#### `close()`\n\nCloses all client connections, removes all handlers and subscriptions, and shuts down the server.\n\n```typescript\nmessenger.close();\n```\n\n## Security Considerations\n\nWhen connecting to iframes from different origins, always specify the exact `targetOrigin` instead of using `'*'`:\n\n```typescript\n// ❌ Insecure - allows any origin\nawait messenger.connect('iframe', iframe!.contentWindow!, { targetOrigin: '*' });\n\n// ✅ Secure - restricts to specific origin\nawait messenger.connect('iframe', iframe!.contentWindow!, {\n  targetOrigin: 'https://trusted-domain.com'\n});\n```\n\n## TypeScript\n\nThis library is written in TypeScript and provides full type safety with generic support.\n\n### Type-safe Requests\n\n```typescript\nimport { MessengerClient } from '@knowledgecode/messenger';\n\ninterface User {\n  id: number;\n  name: string;\n}\n\nconst messenger = new MessengerClient();\n\n// Type-safe request - result is typed as User\nconst user = await messenger.req\u003cUser\u003e('get-user', { id: 123 });\nconsole.log(user.name); // TypeScript knows user has a name property\n```\n\n### Type-safe Subscriptions\n\nThe listener receives `data` as `T | undefined` because messages may be published without data.\n\n```typescript\ninterface StatusUpdate {\n  status: 'online' | 'offline';\n  timestamp: number;\n}\n\n// Type-safe subscribe - data parameter is StatusUpdate | undefined\nmessenger.subscribe\u003cStatusUpdate\u003e('status-change', (data) =\u003e {\n  if (data) {\n    console.log(`Status: ${data.status} at ${data.timestamp}`);\n  }\n});\n```\n\n### Type-safe Handlers\n\nHandlers receive `data` as `T | undefined` because clients may send requests or messages without data.\n\n```typescript\ninterface CalculateRequest {\n  operation: 'add' | 'subtract' | 'multiply' | 'divide';\n  a: number;\n  b: number;\n}\n\ninterface CalculateResponse {\n  result: number;\n}\n\nconst messenger = new MessengerServer('calculator', self);\n\n// Type-safe handler - data parameter is CalculateRequest | undefined\nmessenger.bind\u003cCalculateRequest\u003e('calculate', (data) =\u003e {\n  if (!data) {\n    return { result: 0 };\n  }\n\n  switch (data.operation) {\n    case 'add':\n      return { result: data.a + data.b };\n    case 'subtract':\n      return { result: data.a - data.b };\n    case 'multiply':\n      return { result: data.a * data.b };\n    case 'divide':\n      return { result: data.a / data.b };\n  }\n});\n\n// Client side - response is typed as CalculateResponse\nconst client = new MessengerClient();\nawait client.connect('calculator', self);\n\nconst response = await client.req\u003cCalculateResponse\u003e('calculate', {\n  operation: 'add',\n  a: 5,\n  b: 3\n});\n\nconsole.log(response.result); // TypeScript knows about result property\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fknowledgecode%2Fmessenger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fknowledgecode%2Fmessenger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fknowledgecode%2Fmessenger/lists"}