{"id":25648984,"url":"https://github.com/molvqingtai/comctx","last_synced_at":"2026-01-20T19:06:46.731Z","repository":{"id":275900674,"uuid":"922318609","full_name":"molvqingtai/comctx","owner":"molvqingtai","description":"In any JavaScript environment, use RPC for easy cross-context communication.","archived":false,"fork":false,"pushed_at":"2026-01-13T15:24:03.000Z","size":5387,"stargazers_count":90,"open_issues_count":6,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-01-13T16:23:36.998Z","etag":null,"topics":["broadcastchannel","browser-extension","comlink","electron","iframe","ipcmain","messagechannel","messageport","postmessage","react-native","rpc","service-worker","web-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/molvqingtai.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2025-01-25T22:03:18.000Z","updated_at":"2026-01-13T15:25:36.000Z","dependencies_parsed_at":"2025-02-05T07:28:37.979Z","dependency_job_id":"e22f740b-7926-4c50-8787-d6e3c5720a8c","html_url":"https://github.com/molvqingtai/comctx","commit_stats":null,"previous_names":["molvqingtai/comctx"],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/molvqingtai/comctx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/molvqingtai%2Fcomctx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/molvqingtai%2Fcomctx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/molvqingtai%2Fcomctx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/molvqingtai%2Fcomctx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/molvqingtai","download_url":"https://codeload.github.com/molvqingtai/comctx/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/molvqingtai%2Fcomctx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28609683,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T18:56:40.769Z","status":"ssl_error","status_checked_at":"2026-01-20T18:54:26.653Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["broadcastchannel","browser-extension","comlink","electron","iframe","ipcmain","messagechannel","messageport","postmessage","react-native","rpc","service-worker","web-worker"],"created_at":"2025-02-23T13:52:59.265Z","updated_at":"2026-01-20T19:06:46.191Z","avatar_url":"https://github.com/molvqingtai.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# Comctx\n\nIn any JavaScript environment, use RPC for easy cross-context communication.\n\n[![version](https://img.shields.io/github/v/release/molvqingtai/comctx)](https://www.npmjs.com/package/comctx) [![workflow](https://github.com/molvqingtai/comctx/actions/workflows/ci.yml/badge.svg)](https://github.com/molvqingtai/comctx/actions) [![download](https://img.shields.io/npm/dt/comctx)](https://www.npmjs.com/package/comctx) [![npm package minimized gzipped size](https://img.shields.io/bundlejs/size/comctx)](https://www.npmjs.com/package/comctx) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/molvqingtai/comctx)\n\n```shell\n$ pnpm install comctx\n```\n\n## ✨Introduction\n\n[Comctx](https://github.com/molvqingtai/comctx) aims to solve the communication problem between different contexts in a JavaScript environment. [Comctx](https://github.com/molvqingtai/comctx) has a similar goal to [Comlink](https://github.com/GoogleChromeLabs/comlink), but it's not reinventing the wheel, as [Comlink](https://github.com/GoogleChromeLabs/comlink) relies on [MessagePort](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort), which is limited in some environments ([Issues: 438](https://github.com/GoogleChromeLabs/comlink/issues/438)).\n\n[Comctx](https://github.com/molvqingtai/comctx) enables flexible adaptation to different environments, such as: Web Workers, Browser Extensions, iframes, Electron, etc., making cross-context communication never easier.\n\nhttps://github.com/user-attachments/assets/d1601b54-2669-45d7-b1e5-9bbde1186856\n\n\n## 💡Features\n\n- **Environment Agnostic** - Works across Web Workers, Browser Extensions, iframes, Electron, and more\n- **Bidirectional Communication** - Method calls \u0026 callback support\n- **Zero Copy** - Automatic extraction and zero-copy transfer of transferable objects\n- **Type Safety** - Full TypeScript integration\n- **Lightweight** - 1KB gzipped core\n- **Fault Tolerance** - Backup implementations \u0026 connection heartbeat checks\n\n## 🚀 Quick Start\n\n**Define Shared Service**\n\n```typescript\nimport { defineProxy } from 'comctx'\n\nclass Counter {\n  public value: number\n  constructor(initialValue: number = 0) {\n    this.value = initialValue\n  }\n  async getValue() {\n    return this.value\n  }\n  async onChange(callback: (value: number) =\u003e void) {\n    let oldValue = this.value\n    setInterval(() =\u003e {\n      const newValue = this.value\n      if (oldValue !== newValue) {\n        callback(newValue)\n        oldValue = newValue\n      }\n    })\n  }\n  async increment() {\n    return ++this.value\n  }\n  async decrement() {\n    return --this.value\n  }\n}\n\nexport const [provideCounter, injectCounter] = defineProxy(() =\u003e new Counter(), {\n  namespace: '__comctx-example__'\n})\n```\n\n**Define Adapter**\n```typescript\nimport type { Adapter, SendMessage, OnMessage } from 'comctx'\n\nexport default class CustomAdapter implements Adapter {\n  // Implement message sending\n  sendMessage: SendMessage = (message) =\u003e {\n    postMessage(message)\n  }\n  // Implement message listener\n  onMessage: OnMessage = (callback) =\u003e {\n    const handler = (event: MessageEvent) =\u003e callback(event.data)\n    addEventListener('message', handler)\n    return () =\u003e removeEventListener('message', handler)\n  }\n}\n```\n\n**Provider (Service Provider)**\n\n```typescript\n// Provider side, typically for web-workers, background, etc.\nimport CustomAdapter from 'CustomAdapter'\nimport { provideCounter } from './shared'\n\nconst originCounter = provideCounter(new CustomAdapter())\n\noriginCounter.onChange(console.log)\n```\n\n**Injector (Service Injector)**\n\n```typescript\n// Injector side, typically for the main page, content-script, etc.\nimport CustomAdapter from 'CustomAdapter'\nimport { injectCounter } from './shared'\n\nconst proxyCounter = injectCounter(new CustomAdapter())\n\n// Support for callbacks\nproxyCounter.onChange(console.log)\n\n// Transparently call remote methods\nawait proxyCounter.increment()\nconst count = await proxyCounter.getValue()\n```\n\n- `originCounter` and `proxyCounter` share the same `Counter` instance. `proxyCounter` is a virtual proxy that forwards requests to the `Counter` on the provider side, while `originCounter` directly references the `Counter` itself.\n\n- The injector side cannot directly use `get` and `set`; it must interact with `Counter` via asynchronous methods, but callbacks are supported.\n\n- Since the injector is a virtual proxy, to support operations like `Reflect.has(proxyCounter, 'key')`, you can set `backup` to `true`, which creates a static copy on the injector side that serves as a template without actually running.\n\n- `provideCounter` and `injectCounter` require user-defined adapters for different environments that implement `onMessage` and `sendMessage` methods.\n\n## 🧩 Advanced Usage\n\n### Separate Inject and Provide Definitions\n\nFor multi-package architectures, you can define inject and provide proxies separately to avoid bundling shared code in both packages.\n\nBy default, both provider and injector would bundle the same implementation code, but the injector only needs it for type safety:\n\n```typescript\n// packages/provider/src/index.ts\nimport { defineProxy } from 'comctx'\nimport { Counter } from './shared'\n\nexport const [provideCounter] = defineProxy(() =\u003e new Counter(), {\n  namespace: '__comctx-example__'\n})\n```\n\n```typescript\n// packages/injector/src/index.ts\nimport { defineProxy } from 'comctx'\nimport type { Counter } from './shared'\n\n// Since the injector side is a virtual proxy that doesn't actually run, we can pass an empty object\nexport const [, injectCounter] = defineProxy(() =\u003e ({}) as Counter, {\n  namespace: '__comctx-example__'\n})\n```\n\n### Transfer and Transferable Objects\n\nBy default, every method parameter, return value and object property value is copied ([structured cloning](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)). Comctx performs no internal serialization and natively supports [transferable objects](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects).\n\nIf you want a value to be transferred rather than copied — provided the value is or contains a [Transferable](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects#supported_objects) — you can enable the `transfer` option. When enabled, transferable objects are automatically extracted and transferred using zero-copy semantics:\n\n```typescript\nimport { defineProxy } from 'comctx'\nimport { streamText } from 'ai'\nimport { openai } from '@ai-sdk/openai'\n\nclass AiService {\n  async translate(text: string, targetLanguage: string) {\n    const result = await streamText({\n      model: openai('gpt-4o-mini'),\n      prompt: `Translate to ${targetLanguage}:\\n${text}`\n    })\n    return result.textStream // ReadableStream is transferable when transfer is enabled\n  }\n}\n\nexport const [provideAi, injectAi] = defineProxy(() =\u003e new AiService(), {\n  namespace: '__worker-transfer-example__',\n  transfer: true // Automatically extract and transfer transferable objects\n})\n\n// Usage - receive transferred ReadableStream from an AI translation\nconst ai = injectAi(adapter)\nconst stream = await ai.translate('Hello world', 'zh-CN')\nfor await (const chunk of stream) {\n  console.log(chunk)\n}\n```\n\n#### Adapter Implementation\n\nWhen transfer is enabled, transferable objects are automatically extracted from messages and passed as the transfer parameter to `SendMessage`:\n\n```typescript\n// Transfer-enabled adapter\nexport default class TransferAdapter implements Adapter {\n  sendMessage: SendMessage = (message, transfer) =\u003e {\n    this.worker.postMessage(message, transfer)\n  }\n  // ... rest of implementation\n}\n```\n\n## 🔌 Adapter Interface\n\nTo adapt to different communication channels, implement the following interface:\n\n```typescript\ninterface Adapter\u003cT extends MessageMeta = MessageMeta\u003e {\n  /** Send a message to the other side */\n  sendMessage: (message: Message\u003cT\u003e, transfer: Transferable[]) =\u003e MaybePromise\u003cvoid\u003e\n\n  /** Register a message listener */\n  onMessage: (callback: (message?: Partial\u003cMessage\u003cT\u003e\u003e) =\u003e void) =\u003e MaybePromise\u003cOffMessage | void\u003e\n}\n```\n\n**Note:** [AsyncIterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncIterator) is not a  [Transferable](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects) and cannot be sent across workers. Wrap it with [ReadableStream.from](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/from_static) before returning or sending it so it can be transferred.\n\n## 📖Examples\n\n- [web-worker-example](https://github.com/molvqingtai/comctx/tree/master/examples/web-worker)\n- [shared-worker-example](https://github.com/molvqingtai/comctx/tree/master/examples/shared-worker)\n- [service-worker-example](https://github.com/molvqingtai/comctx/tree/master/examples/service-worker)\n- [worker-transfer-example](https://github.com/molvqingtai/comctx/tree/master/examples/worker-transfer)\n- [browser-extension-example](https://github.com/molvqingtai/comctx/tree/master/examples/browser-extension)\n- [iframe-example](https://github.com/molvqingtai/comctx/tree/master/examples/iframe)\n\n### Web Worker\n\nThis is an example of communication between the main page and a web worker.\n\nsee: [web-worker-example](https://github.com/molvqingtai/comctx/tree/master/examples/web-worker)\n\n**InjectAdapter.ts**\n\n```typescript\nimport { Adapter, SendMessage, OnMessage } from 'comctx'\n\nexport default class InjectAdapter implements Adapter {\n  worker: Worker\n  constructor(path: string | URL) {\n    this.worker = new Worker(path, { type: 'module' })\n  }\n  sendMessage: SendMessage = (message) =\u003e {\n    this.worker.postMessage(message)\n  }\n  onMessage: OnMessage = (callback) =\u003e {\n    const handler = (event: MessageEvent) =\u003e callback(event.data)\n    this.worker.addEventListener('message', handler)\n    return () =\u003e this.worker.removeEventListener('message', handler)\n  }\n}\n```\n\n**ProvideAdapter.ts**\n\n```typescript\nimport { Adapter, SendMessage, OnMessage } from 'comctx'\n\ndeclare const self: DedicatedWorkerGlobalScope\n\nexport default class ProvideAdapter implements Adapter {\n  sendMessage: SendMessage = (message) =\u003e {\n    self.postMessage(message)\n  }\n  onMessage: OnMessage = (callback) =\u003e {\n    const handler = (event: MessageEvent) =\u003e callback(event.data)\n    self.addEventListener('message', handler)\n    return () =\u003e self.removeEventListener('message', handler)\n  }\n}\n```\n\n**web-worker.ts**\n\n```typescript\nimport { provideCounter } from './shared'\nimport ProvideAdapter from './ProvideAdapter'\n\nconst counter = provideCounter(new ProvideAdapter())\n\ncounter.onChange((value) =\u003e {\n  console.log('WebWorker Value:', value)\n})\n```\n\n**main.ts**\n\n```typescript\nimport { injectCounter } from './shared'\nimport InjectAdapter from './InjectAdapter'\n\nconst counter = injectCounter(new InjectAdapter(new URL('./web-worker.ts', import.meta.url)))\n\ncounter.onChange((value) =\u003e {\n  console.log('WebWorker Value:', value) // 1,0\n})\n\nawait counter.getValue() // 0\n\nawait counter.increment() // 1\n\nawait counter.decrement() // 0\n```\n\n### Browser Extension\n\nThis is an example of communication between the content-script and background script.\n\nsee: [browser-extension-example](https://github.com/molvqingtai/comctx/tree/master/examples/browser-extension)\n\n**InjectAdapter.ts**\n\n```typescript\nimport browser from 'webextension-polyfill'\nimport { Adapter, Message, SendMessage, OnMessage } from 'comctx'\n\nexport interface MessageMeta {\n  url: string\n}\n\nexport default class InjectAdapter implements Adapter\u003cMessageMeta\u003e {\n  sendMessage: SendMessage\u003cMessageMeta\u003e = (message) =\u003e {\n    browser.runtime.sendMessage(browser.runtime.id, { ...message, meta: { url: document.location.href } })\n  }\n  onMessage: OnMessage\u003cMessageMeta\u003e = (callback) =\u003e {\n    const handler = (message?: Partial\u003cMessage\u003cMessageMeta\u003e\u003e) =\u003e {\n      callback(message)\n    }\n    browser.runtime.onMessage.addListener(handler)\n    return () =\u003e browser.runtime.onMessage.removeListener(handler)\n  }\n}\n```\n\n**ProvideAdapter.ts**\n\n```typescript\nimport browser from 'webextension-polyfill'\nimport { Adapter, Message, SendMessage, OnMessage } from 'comctx'\n\nexport interface MessageMeta {\n  url: string\n}\n\nexport default class ProvideAdapter implements Adapter\u003cMessageMeta\u003e {\n  sendMessage: SendMessage\u003cMessageMeta\u003e = async (message) =\u003e {\n    const tabs = await browser.tabs.query({ url: message.meta.url })\n    tabs.map((tab) =\u003e browser.tabs.sendMessage(tab.id!, message))\n  }\n\n  onMessage: OnMessage\u003cMessageMeta\u003e = (callback) =\u003e {\n    const handler = (message?: Partial\u003cMessage\u003cMessageMeta\u003e\u003e) =\u003e {\n      callback(message)\n    }\n    browser.runtime.onMessage.addListener(handler)\n    return () =\u003e browser.runtime.onMessage.removeListener(handler)\n  }\n}\n```\n\n**background.ts**\n\n```typescript\nimport { provideCounter } from './shared'\nimport ProvideAdapter from './ProvideAdapter'\n\nconst counter = provideCounter(new ProvideAdapter())\n\ncounter.onChange((value) =\u003e {\n  console.log('Background Value:', value) // 1,0\n})\n```\n\n**content-script.ts**\n\n```typescript\nimport { injectCounter } from './shared'\nimport InjectAdapter from './InjectAdapter'\n\nconst counter = injectCounter(new InjectAdapter())\n\ncounter.onChange((value) =\u003e {\n  console.log('Background Value:', value) // 1,0\n})\n\nawait counter.getValue() // 0\n\nawait counter.increment() // 1\n\nawait counter.decrement() // 0\n```\n\n### IFrame\n\nThis is an example of communication between the main page and an iframe.\n\nsee: [iframe-example](https://github.com/molvqingtai/comctx/tree/master/examples/iframe)\n\n**InjectAdapter.ts**\n\n```typescript\nimport { Adapter, SendMessage, OnMessage } from 'comctx'\n\nexport default class InjectAdapter implements Adapter {\n  sendMessage: SendMessage = (message) =\u003e {\n    window.postMessage(message, '*')\n  }\n  onMessage: OnMessage = (callback) =\u003e {\n    const handler = (event: MessageEvent) =\u003e callback(event.data)\n    window.addEventListener('message', handler)\n    return () =\u003e window.removeEventListener('message', handler)\n  }\n}\n```\n\n**ProvideAdapter.ts**\n\n```typescript\nimport { Adapter, SendMessage, OnMessage } from 'comctx'\n\nexport default class ProvideAdapter implements Adapter {\n  sendMessage: SendMessage = (message) =\u003e {\n    window.parent.postMessage(message, '*')\n  }\n  onMessage: OnMessage = (callback) =\u003e {\n    const handler = (event: MessageEvent) =\u003e callback(event.data)\n    window.parent.addEventListener('message', handler)\n    return () =\u003e window.parent.removeEventListener('message', handler)\n  }\n}\n```\n\n**iframe.ts**\n\n```typescript\nimport { provideCounter } from './shared'\nimport ProvideAdapter from './ProvideAdapter'\n\nconst counter = provideCounter(new ProvideAdapter())\n\ncounter.onChange((value) =\u003e {\n  console.log('iframe Value:', value) // 1,0\n})\n```\n\n**main.ts**\n\n```typescript\nimport { injectCounter } from './shared'\nimport InjectAdapter from './InjectAdapter'\n\nconst counter = injectCounter(new InjectAdapter())\n\ncounter.onChange((value) =\u003e {\n  console.log('iframe Value:', value) // 1,0\n})\n\nawait counter.getValue() // 0\n\nawait counter.increment() // 1\n\nawait counter.decrement() // 0\n```\n\n## 🩷Thanks\n\nThe inspiration for this project comes from [@webext-core/proxy-service](https://webext-core.aklinker1.io/proxy-service/installation/), but [Comctx](https://github.com/molvqingtai/comctx) aims to be a better version of it.\n\n## 📃License\n\nThis project is licensed under the MIT License - see the [LICENSE](https://github.com/molvqingtai/comctx/blob/master/LICENSE) file for details\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmolvqingtai%2Fcomctx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmolvqingtai%2Fcomctx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmolvqingtai%2Fcomctx/lists"}