{"_id":"@davestewart/extension-bus","_rev":"5-f95342ee6ff04f5143cb0612dc703ca9","name":"@davestewart/extension-bus","description":"Universal message bus for Chromium and Firefox web extensions","dist-tags":{"latest":"1.5.0"},"versions":{"1.2.0":{"name":"@davestewart/extension-bus","version":"1.2.0","author":{"name":"Dave Stewart"},"license":"MIT","_id":"@davestewart/extension-bus@1.2.0","maintainers":[{"name":"davestewart","email":"dev@davestewart.co.uk"}],"homepage":"https://github.com/davestewart/extension-bus#readme","bugs":{"url":"https://github.com/davestewart/extension-bus/issues"},"dist":{"shasum":"5e360aaa8da93fd3582e544e87672a5fdb1b9715","tarball":"https://registry.npmjs.org/@davestewart/extension-bus/-/extension-bus-1.2.0.tgz","fileCount":9,"integrity":"sha512-jValFR8eN8mjoysm9VxJLnlYvHGhULBFxAUSFO6hufYfENFu3VJazfqrNCI9fXXwr4xlWdHyODig3Itp2U2Ong==","signatures":[{"sig":"MEUCIAeY8ULN7kyMNbRsFX/spF7pOsEHD+W9QtVoRHJkGh4dAiEA3ppf/rkkVqxAWBAqniBLdn7Z0lukbXaLCQ0NK1vkGoA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":49901},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","gitHead":"7606992e0f5d37a4bf4f720b1bac9460fccca0da","private":false,"scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"rimraf dist && tsup src/index.ts --sourcemap --dts --format esm,cjs","postbuild":"npm run demo:prepare && npm run demo:copy-mv2 && npm run demo:copy-mv3","demo:prepare":"rimraf demo/mv2/bus demo/mv3/bus && mkdir demo/mv2/bus demo/mv3/bus","demo:copy-mv2":"cp ./dist/index.mjs ./dist/index.mjs.map ./demo/mv2/bus","demo:copy-mv3":"cp ./dist/index.mjs ./dist/index.mjs.map ./demo/mv3/bus"},"_npmUser":{"name":"davestewart","email":"dev@davestewart.co.uk"},"repository":{"url":"git+https://github.com/davestewart/extension-bus.git","type":"git"},"_npmVersion":"8.19.4","description":"Universal message bus for Chromium and Firefox web extensions","directories":{},"_nodeVersion":"16.20.2","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.1","eslint":"^8.56.0","rimraf":"^5.0.5","typescript":"^5.3.3","@types/chrome":"^0.0.256","eslint-plugin-n":"^16.6.2","eslint-plugin-import":"^2.29.1","eslint-plugin-promise":"^6.1.1","eslint-config-standard":"^17.1.0"},"_npmOperationalInternal":{"tmp":"tmp/extension-bus_1.2.0_1705407122758_0.6303144230628572","host":"s3://npm-registry-packages"}},"1.2.1":{"name":"@davestewart/extension-bus","version":"1.2.1","author":{"name":"Dave Stewart"},"license":"MIT","_id":"@davestewart/extension-bus@1.2.1","maintainers":[{"name":"davestewart","email":"dev@davestewart.co.uk"}],"homepage":"https://github.com/davestewart/extension-bus#readme","bugs":{"url":"https://github.com/davestewart/extension-bus/issues"},"dist":{"shasum":"6c66f0d926c9e07079ccfff1e972b24cc208623d","tarball":"https://registry.npmjs.org/@davestewart/extension-bus/-/extension-bus-1.2.1.tgz","fileCount":9,"integrity":"sha512-I1bi9aBOhsrq9DhDOFy/zt5th27FHlrHoFGr/cxC2XiWxXW+NYn6wv4qFh8NXEVWRXsn/JA/ncP1q+9X1q31hg==","signatures":[{"sig":"MEYCIQCpduizuTSRYeNdXuMBqNk5wszQisblp8MfiJiw7pzMswIhAN1n61lgvdkroYmUUFDATf60kwhJ6TY9lFkcI9v42V9a","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":50041},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","gitHead":"16a210e61b3a745475d25eee61013d6155210927","private":false,"scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"rimraf dist && tsup src/index.ts --sourcemap --dts --format esm,cjs","postbuild":"npm run demo:prepare && npm run demo:copy-mv2 && npm run demo:copy-mv3","demo:prepare":"rimraf demo/mv2/bus demo/mv3/bus && mkdir demo/mv2/bus demo/mv3/bus","demo:copy-mv2":"cp ./dist/index.mjs ./dist/index.mjs.map ./demo/mv2/bus","demo:copy-mv3":"cp ./dist/index.mjs ./dist/index.mjs.map ./demo/mv3/bus"},"_npmUser":{"name":"davestewart","email":"dev@davestewart.co.uk"},"repository":{"url":"git+https://github.com/davestewart/extension-bus.git","type":"git"},"_npmVersion":"8.19.4","description":"Universal message bus for Chromium and Firefox web extensions","directories":{},"_nodeVersion":"16.20.2","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.1","eslint":"^8.56.0","rimraf":"^5.0.5","typescript":"^5.3.3","@types/chrome":"^0.0.256","eslint-plugin-n":"^16.6.2","eslint-plugin-import":"^2.29.1","eslint-plugin-promise":"^6.1.1","eslint-config-standard":"^17.1.0"},"_npmOperationalInternal":{"tmp":"tmp/extension-bus_1.2.1_1705409156733_0.4913397301500775","host":"s3://npm-registry-packages"}},"1.2.2":{"name":"@davestewart/extension-bus","version":"1.2.2","author":{"name":"Dave Stewart"},"license":"MIT","_id":"@davestewart/extension-bus@1.2.2","maintainers":[{"name":"davestewart","email":"dev@davestewart.co.uk"}],"homepage":"https://github.com/davestewart/extension-bus#readme","bugs":{"url":"https://github.com/davestewart/extension-bus/issues"},"dist":{"shasum":"32b35e0efafd6d6960921ffe2dfa59bed5646708","tarball":"https://registry.npmjs.org/@davestewart/extension-bus/-/extension-bus-1.2.2.tgz","fileCount":9,"integrity":"sha512-bLk5gfpeTB3bAO8S5FhORZ0vxNKPEfU70kw++SVT5+eS6mmH10+QtcrTavdAVnHRIwVqNUFGGjC+CMB66Vs0iQ==","signatures":[{"sig":"MEUCIQC3uVpEbw8vSvRfQS9sBOZMZM/Y6Ax1qAloVTe1SWl69gIgPQTBVNrFWY5123f7/lMk7fvjhOIH/9g6LdiWqk6ak7A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":51203},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","gitHead":"b9e9d26006aaa714cd853e6e8bcdc4eb0622ce2a","private":false,"scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"rimraf dist && tsup src/index.ts --sourcemap --dts --format esm,cjs","postbuild":"npm run demo:prepare && npm run demo:copy-mv2 && npm run demo:copy-mv3","demo:prepare":"rimraf demo/mv2/bus demo/mv3/bus && mkdir demo/mv2/bus demo/mv3/bus","demo:copy-mv2":"cp ./dist/index.mjs ./dist/index.mjs.map ./demo/mv2/bus","demo:copy-mv3":"cp ./dist/index.mjs ./dist/index.mjs.map ./demo/mv3/bus"},"_npmUser":{"name":"davestewart","email":"dev@davestewart.co.uk"},"repository":{"url":"git+https://github.com/davestewart/extension-bus.git","type":"git"},"_npmVersion":"8.19.4","description":"Universal message bus for Chromium and Firefox web extensions","directories":{},"_nodeVersion":"16.20.2","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.1","eslint":"^8.56.0","rimraf":"^5.0.5","typescript":"^5.3.3","@types/chrome":"^0.0.256","eslint-plugin-n":"^16.6.2","eslint-plugin-import":"^2.29.1","eslint-plugin-promise":"^6.1.1","eslint-config-standard":"^17.1.0"},"_npmOperationalInternal":{"tmp":"tmp/extension-bus_1.2.2_1705432388086_0.8685798405894931","host":"s3://npm-registry-packages"}},"1.3.1":{"name":"@davestewart/extension-bus","version":"1.3.1","author":{"name":"Dave Stewart"},"license":"MIT","_id":"@davestewart/extension-bus@1.3.1","maintainers":[{"name":"davestewart","email":"dev@davestewart.co.uk"}],"homepage":"https://github.com/davestewart/extension-bus#readme","bugs":{"url":"https://github.com/davestewart/extension-bus/issues"},"dist":{"shasum":"60a9db3dff8982f6e1bb6e53a60903aab53a46e3","tarball":"https://registry.npmjs.org/@davestewart/extension-bus/-/extension-bus-1.3.1.tgz","fileCount":9,"integrity":"sha512-ZNBYLSe3F5Yt/CoI/LoAAvA6LgDj3FN1SGqCCfcaJxC0BiLuj7T2gLxyOohOTEnOibsrmCYyxzniznVS+6ugVA==","signatures":[{"sig":"MEYCIQDs3mLVYDItYBT3GO4xMyKMq5jsOymDUe8O3sbJ2X2PBAIhAOKFlAI+2Z6s4fmKTFXNEYHAale59wHgbjZdEetUmJzQ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":54414},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","gitHead":"eca86a1671dab201a0a95da7cdc746d015b6d614","private":false,"scripts":{"dev":"nodemon -w src -e ts --exec npm run build","test":"echo \"Error: no test specified\" && exit 1","build":"rimraf dist && tsup src/index.ts --sourcemap --dts --format esm,cjs","postbuild":"npm run demo:prepare && npm run demo:copy-mv2 && npm run demo:copy-mv3","demo:prepare":"rimraf demo/mv2/bus demo/mv3/bus && mkdir demo/mv2/bus demo/mv3/bus","demo:copy-mv2":"cp ./dist/index.mjs ./dist/index.mjs.map ./demo/mv2/bus","demo:copy-mv3":"cp ./dist/index.mjs ./dist/index.mjs.map ./demo/mv3/bus"},"_npmUser":{"name":"davestewart","email":"dev@davestewart.co.uk"},"repository":{"url":"git+https://github.com/davestewart/extension-bus.git","type":"git"},"_npmVersion":"8.19.4","description":"Universal message bus for Chromium and Firefox web extensions","directories":{},"_nodeVersion":"16.20.2","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.1","eslint":"^8.56.0","rimraf":"^5.0.5","nodemon":"^3.0.3","typescript":"^5.3.3","@types/chrome":"^0.0.256","eslint-plugin-n":"^16.6.2","eslint-plugin-import":"^2.29.1","eslint-plugin-promise":"^6.1.1","eslint-config-standard":"^17.1.0"},"_npmOperationalInternal":{"tmp":"tmp/extension-bus_1.3.1_1705673643944_0.3738748072471356","host":"s3://npm-registry-packages"}},"1.4.1":{"name":"@davestewart/extension-bus","version":"1.4.1","author":{"name":"Dave Stewart"},"license":"MIT","_id":"@davestewart/extension-bus@1.4.1","maintainers":[{"name":"davestewart","email":"dev@davestewart.co.uk"}],"homepage":"https://github.com/davestewart/extension-bus#readme","bugs":{"url":"https://github.com/davestewart/extension-bus/issues"},"dist":{"shasum":"4dbfe8d73cd550e5d0240eabfbe35d9fdd86922f","tarball":"https://registry.npmjs.org/@davestewart/extension-bus/-/extension-bus-1.4.1.tgz","fileCount":9,"integrity":"sha512-wKbRylgwmNdpLk5fb2oyMNoWo9y0M22ClAC0N2342VUT5PzudI3X2FHd/d7UuHhGPXMqy3e+k37poWDShPYGXg==","signatures":[{"sig":"MEUCIQCPLpln4zgD1tMDFuh+4B0lXp5RmBt85gtaaSPT3rb+kQIgHqVPaPAJ36h5LxJZStlWoCvT5WrE+jksG3bcWwouCF0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":66162},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","gitHead":"61ebc858582750506d175fda5b0d206954cdc16b","private":false,"scripts":{"dev":"nodemon -w src -e ts --exec npm run build","test":"echo \"Error: no test specified\" && exit 1","build":"rimraf dist && tsup src/index.ts --sourcemap --dts --format esm,cjs","postbuild":"npm run demo:prepare && npm run demo:copy-mv2 && npm run demo:copy-mv3","demo:prepare":"rimraf demo/mv2/bus demo/mv3/bus && mkdir demo/mv2/bus demo/mv3/bus","demo:copy-mv2":"cp ./dist/index.mjs ./dist/index.mjs.map ./demo/mv2/bus","demo:copy-mv3":"cp ./dist/index.mjs ./dist/index.mjs.map ./demo/mv3/bus"},"_npmUser":{"name":"davestewart","email":"dev@davestewart.co.uk"},"repository":{"url":"git+https://github.com/davestewart/extension-bus.git","type":"git"},"_npmVersion":"8.19.4","description":"Universal message bus for Chromium and Firefox web extensions","directories":{},"_nodeVersion":"16.20.2","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.1","eslint":"^8.56.0","rimraf":"^5.0.5","nodemon":"^3.0.3","typescript":"^5.3.3","@types/chrome":"^0.0.256","eslint-plugin-n":"^16.6.2","eslint-plugin-import":"^2.29.1","eslint-plugin-promise":"^6.1.1","eslint-config-standard":"^17.1.0"},"_npmOperationalInternal":{"tmp":"tmp/extension-bus_1.4.1_1706182373816_0.6862363266728366","host":"s3://npm-registry-packages"}},"1.5.0":{"name":"@davestewart/extension-bus","private":false,"version":"1.5.0","description":"Universal message bus for Chromium and Firefox web extensions","main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","scripts":{"dev":"nodemon -w src -e ts --exec npm run build","build":"rimraf dist && tsup src/index.ts --sourcemap --dts --format esm,cjs","postbuild":"npm run demo:prepare && npm run demo:copy-mv2 && npm run demo:copy-mv3","demo:prepare":"rimraf demo/mv2/bus demo/mv3/bus && mkdir demo/mv2/bus demo/mv3/bus","demo:copy-mv2":"cp ./dist/index.mjs ./dist/index.mjs.map ./demo/mv2/bus","demo:copy-mv3":"cp ./dist/index.mjs ./dist/index.mjs.map ./demo/mv3/bus","test":"echo \"Error: no test specified\" && exit 1"},"author":{"name":"Dave Stewart"},"license":"MIT","homepage":"https://github.com/davestewart/extension-bus#readme","repository":{"type":"git","url":"git+https://github.com/davestewart/extension-bus.git"},"bugs":{"url":"https://github.com/davestewart/extension-bus/issues"},"devDependencies":{"@types/chrome":"^0.0.256","eslint":"^8.56.0","eslint-config-standard":"^17.1.0","eslint-plugin-import":"^2.29.1","eslint-plugin-n":"^16.6.2","eslint-plugin-promise":"^6.1.1","nodemon":"^3.0.3","rimraf":"^5.0.5","tsup":"^8.0.1","typescript":"^5.3.3"},"_id":"@davestewart/extension-bus@1.5.0","gitHead":"4469aaed71ceda7dd97dbaec03c4ab0fd5aa3970","_nodeVersion":"20.11.1","_npmVersion":"10.2.4","dist":{"integrity":"sha512-IN3205JvwVoeKpyUZRiy+VserC9mL6qdZcIB8U4+hIaoyY6yUNigEwa6fCJObW7IoWXT6NlewCkI3drObimP3A==","shasum":"226de05c68200775f962a072477b0314f1d214d5","tarball":"https://registry.npmjs.org/@davestewart/extension-bus/-/extension-bus-1.5.0.tgz","fileCount":9,"unpackedSize":69649,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIG2RsaYnUf6UIM/UzLH1H84aWhI00FCNT+89iHl8YO6jAiEAi48dRhA3WoAYRq2+Kj1WqfJteKQWHU0wCv2YwC5PK9A="}]},"_npmUser":{"name":"davestewart","email":"dev@davestewart.co.uk"},"directories":{},"maintainers":[{"name":"davestewart","email":"dev@davestewart.co.uk"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/extension-bus_1.5.0_1717589321417_0.12568940066401146"},"_hasShrinkwrap":false}},"time":{"created":"2024-01-16T12:12:02.691Z","modified":"2024-06-05T12:08:41.764Z","1.2.0":"2024-01-16T12:12:02.906Z","1.2.1":"2024-01-16T12:45:56.925Z","1.2.2":"2024-01-16T19:13:08.247Z","1.3.1":"2024-01-19T14:14:04.164Z","1.4.1":"2024-01-25T11:32:54.004Z","1.5.0":"2024-06-05T12:08:41.583Z"},"maintainers":[{"name":"davestewart","email":"dev@davestewart.co.uk"}],"author":{"name":"Dave Stewart"},"repository":{"type":"git","url":"git+https://github.com/davestewart/extension-bus.git"},"license":"MIT","homepage":"https://github.com/davestewart/extension-bus#readme","bugs":{"url":"https://github.com/davestewart/extension-bus/issues"},"readme":"# Extension Bus\n\n> Universal message bus for web extensions\n\n![splash](https://raw.githubusercontent.com/davestewart/extension-bus/main/splash.png)\n\n## Abstract\n\nThe Web Extensions API provides a way to communicate between processes by way of [message passing](https://developer.chrome.com/docs/extensions/mv2/messaging).\n\nHowever, setting up a robust, consistent and flexible messaging implementation is surprisingly complex.\n\nThis package provides an elegant solution, with:\n\n- simple cross-process messaging\n- named buses to easily target processes\n- nested handlers with an API-like interface \n- transparent handling of sync and async handlers\n- transparent handling of process and handler errors\n- transparent handling of internal and external calls\n- a consistent interface for process, tab and external calls\n\nOnce configured with targets and handlers typical [messaging code](#sending-a-message) is as follows:\n\n```ts\nconst result = await bus.call('some/handler', payload)\n```\n\nAnd with consistent handling of [errors and edge cases](#error-handling) messaging both simple and intuitive.\n\n## Usage\n\n### Installation\n\nInstall from NPM:\n\n```bash\nnpm i @davestewart/extension-bus\n```\n\nAlternatively, you can shorten imports with an alias, for example `bus`:\n\n```bash\nnpm i bus@npm:@davestewart/extension-bus\n```\n\n```ts\n// easier import\nimport { bus } from 'bus'\t\n```\n\n### Creating a bus\n\nFor each process, i.e. `background`, `popup`,  `content`, `page` :\n\n- create a named `Bus`\n- add handler functions\n- optionally specify a `target`\n- optionally configure `external` access\n\n```js\nimport { makeBus } from 'extension-bus'\n\n// named process\nconst bus = makeBus('popup', {\n  // optionally target a specific process\n  target: 'background',\n  \n  // handle incoming requests\n  handlers: {\n    foo (value, sender) { ... },\n    bar (value, { tab }) { ... },\n  },\n\n  // allow external connection\n  external: true,\n})\n```\n\n#### TypeScript\n\nIf you prefer to declare `handlers` separately, type their parameters with the `Handlers` type:\n\n```ts\nimport { type Handlers } from 'extension-bus'\n\nexport const handlers: Handlers = {\n  // number, chrome.runtime.MessageSender\n  foo (value: number, { tab }) {\n    const url = tab?.url\n  }\n}\n```\n\nNote that:\n\n- you can name a `bus` anything, i.e. `content`, `account`, `gmail`, etc\n- any `target` must be the name of another `bus`, or `*` to target all buses (the default)\n- `handlers` may be nested, then targeted using  `/` syntax, i.e. `'baz/qux'`\n- new handlers may be added via `add()`, i.e. `bus.add('baz': { qux })`\n\n### Sending a message\n\n#### To other processes\n\nTo send a message to buses in one or more processes, call their handlers by `path`:\n\n```js\n// flat\nconst result = await bus.call('greet', 'hello')\n\n// nested\nconst result = await bus.call('foo/bar/baz', payload)\n\n// override target\nconst result = await bus.call('popup:greet', 'hello')\n```\n\nNote that calls will *always* complete; use `await` to receive returned values ([errors](#error-handling) always return `null`)\n\n#### To other tabs\n\nTo target tab content scripts, use `callTab()`:\n\n```js\n// call a specific tab\nconst result = await bus.callTab(123, 'greet', 'hello')\n\n// call the current tab (useful from the extension's action icon)\nconst result = await bus.callTab(true, 'greet', 'hello')\n```\n\n#### To other extensions\n\nTo target buses in other extensions, use `callExtension()`:\n\n```js\nconst result = await bus.callExtension('<extensionId>', 'account/login', { username, password })\n```\n\nSee the [Receiving messages](#from-web-pages-or-other-extensions) section for more information. \n\n#### TypeScript\n\nIf you want to type any `call()` functions' `result` and `payload`, pass the type parameters in that order:\n\n```ts\nconst window = await bus.call<Window, number>('windows/get', 1)\n```\n\nIf you think a call may *not* complete (missing tab, popup closed, etc) pass a `null` union as the result type:\n\n```ts\nconst window = await bus.call<Window | null>('windows/get', 1000)\nif (window) {\n  ...\n}\n```\n\nSee the [Error handling](#error-handling) section for more information.\n\n### Receiving a message\n\n#### From other processes\n\nMessages that successfully target a bus will be routed to the correct handler:\n\n```ts\n// content script\nconst result = await bus.call('bookmarks/related', 'www.google.com')\n```\nOnce a handler is targeted, you have a few additional conveniences:\n\n```ts\n// background script\nimport { type Handlers } from 'extension-bus'\n\n// Handlers type automatically types `sender` property\nconst handlers: Handlers = {\n  bookmarks: {\n    async related (domain: string, { tab }) {\n      // reference sender\n      if (tab.url?.includes(domain)) {\n        // reference sibling handlers\n        const bookmarks = await this.search(domain)\n\n        // optionally return a value\n        return { bookmarks }\n      }\n    },\n    \n    search (domain: string) {\n      return chrome.tabs.query({ url: `https://${domain}/*` })\n    }\n  }\n}\n```\n\nNote that:\n\n- the first parameter is the call payload (can be any JSON-serializable value)\n- the second parameter is the `sender` context (which _may_ contain a tab)\n- handlers are scoped to their containing block (so `this` targets siblings)\n- return a value to respond to the `source` bus\n\n#### From web pages or other extensions\n\nYou can configure whether a bus should be able to receive external messages:\n\n```ts\nconst bus = makeBus('background', {\n  // always accept messages\n  external: true,\n\n  // accept calls only to these paths (supports wildcards)\n  external: [\n    'account/login',\n    'user/*',\n  ],\n\n  // programatically accept messages\n  external (path: string, sender: chrome.runtime.MessageSender): boolean {\n    return sender.tab.url.startsWith('https://yourdomain.com') && path.startsWith('account/')\n  },\n})\n```\n\nNote: \n\n- it's generally more reliable to receive messages _only_ in the background process\n- if the predicate fails the sending extension will receive no response\n\n#### Sending from a non-Extension Bus extension \n\nIf you want to message an Extension Bus extension from a non-Extension Bus extension, pass an object with `path` and optional `data` properties:\n\n```ts\nchrome.runtime.sendMessage('<extensionId>', { path: 'path/to/handler', data: 123 }, function (response) {\n  if (response) {\n    console.log(response.result)\n  }\n})\n```\n\nNote however, that Extension Bus is designed to be used across multiple extensions.\n\n### API\n\nSee the types file for the full API:\n\n- https://github.com/davestewart/extension-bus/tree/main/src/types.ts\n\n## Error handling\n\nExtension Bus guarantees all calls complete, but an \"error\" state occurs if:\n\n- the targeted bus or tab does not exist\n- no handler paths were matched\n- a matched handler errors or rejects a promise\n- extension source code was updated but not reloaded \n\nFailed calls return `null`, and may trigger a warning if configured:\n\n```\nextension-bus[popup] ReferenceError at \"background:foo/bar\": foo is not defined\n```\n\nIf you're not sure if there was an error, check the `bus.error` property:\n\n```js\nconst result = await bus.call('foo/bar')\nif (result === null && bus.error) {\n  // handle error\n}\n```\n\nIf there is an error, the property will contain further information:\n\n```js\n{\n  code: 'handler_error',\n  message: 'foo is not defined',\n  target: 'background:foo/bar',\n}\n```\n\nThe following table explains the error codes:\n\n| Code            | Message                                                       | Reason                                                                                                                                                           |\n|-----------------|---------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `no_response`   | The message port closed before a response was received.       | There were no `target` buses loaded that matched the source bus' `target` property, or multiple buses were called via (`*`) and none contained matching handlers |\n|                 | Could not establish connection. Receiving end does not exist. | The targeted tab didn't exist, was discarded, was never loaded, or wasn't reloaded after reloading the extension                                                 |\n| `no_handler`    | No handler                                                    | A named `target` bus was found, but did not contain a handler at the supplied `path`                                                                             |\n| `handler_error` | *The error message*                                           | A handler was found, but threw an error when called (see the `target`'s console for the full `error` object)                                                     |\n\nNote that because of the way message passing works, a `no_handler` error will only be recorded when targeting a **single** *named* bus. This is because when targeting multiple (bus) listeners, the first listener to reply wins, so in order not to prevent a _potential_ matched bus from replying, unmatched buses **must** stay quiet; thus if _no_ buses match or contain handlers, the error can only be `no_response`.\n\nFor example:\n\n```js\nawait bus.call('*:unknown') || bus.error?.code // 'no_response'\nawait bus.call('background:unknown') || bus.error?.code // 'no_handler'\n```\n\n### Error handling options\n\nTo modify how errors are handled, configure the `onError` option:\n\n```js\nconst bus = makeBus('popup', {\n  // warns in the console (unless error is \"no_response\") and returns null\n  onError: 'warn',\n\n  // rejects a BusError object, and should be handled by try/catch or .catch(err)\n  onError: 'reject',\n\n  // custom function, from which you can return a value\n  onError: (request: BusRequest, response: BusResponse, error: Bus) => { ... },\n})\n```\n\n### A note about error trapping\n\nHandler execution is wrapped in a `try/catch` and uses `console.warn()` to log errors.\n\nThe console output will contain a call stack so should be sufficient for debugging purposes – though logging errors is really just a courtesy to prevent them being swallowed by the `catch`. If you have code that may error, you should handle it _within_ the target handler function, rather than letting errors leak into the bus.\n\n### Writing code in development\n\nWriting successful message handling is complicated by the fact that as code is updated / reloaded, connections are replaced, and Chrome can error (see above table).\n\nTo successfully write, run, *re*write and *re*run code which sends messages between processes:\n\n- For `page` and `background` processes, reload the process using `Cmd+R`/`F5`\n- For `popup` scripts, reopen the popup to load the new script\n- For `content` scripts:\n  - make sure to reload both the extension **and** content scripts tabs\n  - if you're having trouble targeting the new script context in the console's \"context\" dropdown, open the URL in a new tab\n\n\n## Demo\n\nThe package is compatible with both MV2 and MV3 and ships with near-identical demos for both:\n\n![screenshot](https://raw.githubusercontent.com/davestewart/extension-bus/typescript/demo/assets/screenshot.png)\n\nYou can check the source code at:\n\n- https://github.com/davestewart/extension-bus/tree/main/demo\n\nIn each demo, each of the main processes have a named `bus` configured, and each of them sends messages to one or more processes:\n\n| Process    | Sends to              | Registered handlers | Demonstrates                            |\n|------------|-----------------------|---------------------|:----------------------------------------|\n| Popup      | All, Page, Background | `pass`, `fail`      | Returning and erroring calls            |\n| Page       | All, Page, Background | `pass`, `fail`      | Returning and erroring calls            |\n| Background | All                   | `pass`, `fail`      | Returning and erroring calls            |\n|            |                       | `handle`            | Non-returning call                      |\n|            |                       | `nested/hello`      | Nested handler                          |\n|            |                       | `delay`             | Async handler                           |\n|            |                       | `bound`             | Referencing a sibling handler           |\n|            |                       | `tabs/identify`     | Returning a content script its tab `id` |\n|            |                       | `tabs/update`       | Executing a script in the sending tab   |\n| Content    | Background            | `pass`, `fail`,     | Returning and erroring calls            |\n|            |                       | `update`            | Calling a content script by tab id      |\n\nThe examples demonstrate:\n\n- a handler called `pass()` which always returns a result\n- a handler called `fail()` which will throw an error and receive `null`\n- sync and async handlers\n- nested handlers\n- passing payloads\n- calling content scripts by id\n\nFor more information and usage examples, check the comments in each of the functions in the demo `.js` files.\n\nNote that the extension will need to be reloaded if you make changes!\n\n### Installation\n\nTo install:\n\n- clone this repository\n- From Chrome's extensions page\n  - Toggle on \"Developer mode\"\n  - Click \"Load unpacked\"\n  - Choose the appropriate `demo` folder in the cloned repo\n\nTo run the MV3 demo in Firefox, modify the `background` key in the `manifest.json` file as follows:\n\n```json\n{\n  \"background\": {\n    \"scripts\": [\n      \"app/background/background.js\"\n    ]\n  }\n}\n```\n\n### Getting started\n\nJump in and play with each of the extension's processes / buses in the browser.\n\nNote that this will be a mix of UI for `popup` and `pages` and DevTools for `background` and `content`.\n\nAs you click the buttons in the page, or make calls in the DevTools, watch to see related pages update or console\nentries appear.\n\n### Popup and Page\n\nTo use the popup bus, click the Extension's icon in the toolbar.\n\nOnce the popup is open, or an extension page is loaded, you can:\n\n- click the buttons to call handlers on buses in other processes:\n  - **Call All** – calls all registered and loaded buses\n  - **Call Background** – calls the `background` bus only\n  - **Call Content** – calls the current tab's `content` bus (tab must have reloaded)\n  - **Call Page** – calls the `pass()` handler in any loaded `page` bus\n  - **Fail Page** – calls the `fail()` handler in any loaded `page` bus\n- click the **Add Page** button to add a new `page` tab (which are also registered to send and receive)\n\nNote that:\n\n- sending and receiving messages (from other processes / buses) will update the table\n- not all processes may exist at any particular time:\n  - if no `page` tabs are open\n  - if `content` scripts are not yet loaded or have been changed since last load\n\n### Background and Content\n\nBackground and content buses can be interacted with via the DevTools.\n\n#### Background\n\nOpen the `background` page from the extension's Options page, and the `content` script using the DevTools for open tabs.\n\nAs an example, here's how you might call other buses from the `background` page:\n\n```js\n// call all processes\nawait bus.call('pass', 'hello from background')\n\n// call an open page function\nawait bus.call('page:pass', 'hello from background') || bus.error\n\n// call an open page function that fails\nawait bus.call('page:fail', 'hello from background') || bus.error\n\n// call popup (if open)\nawait bus.call('popup:pass', 'hello from background') || bus.error\n\n// target a content script\n// reload any tab and check the console for the tab id, e.g. 334068351\nawait bus.callTab(334068351, 'pass')\n\n// set active tab's body color to red (must be an https:// page)\nchrome.windows.getLastFocused(function (window) {\n  chrome.tabs.query({ active: true, windowId: window.id }, async function (tabs) {\n    const [tab] = tabs\n    const result = await bus.call(tab.id, 'update', 'red')\n    console.log(result)\n  })\n})\n```\n\nThe background bus also exposes two paths to external messaging. See the [section above](#from-web-pages-or-other-extensions) for more information, but from another extension you should _only_ be able to call `pass` or `nested/hello`:\n\n```ts\nconst result = await bus.callExtension('<extensionId>', 'pass')\n```\n\nTo test this, you can install the MV2 extension and the MV3 extension, and message one from the other.\n\n### Content\n\nThe content script example is set up to `reject` errors, so you can play with `try/catch ` here if you prefer that way of working.\n\nIn the console, select the \"Extension Bus Demo\" option from the script context dropdown, then:\n\n```js\nbus.call('fail').catch((err: BusError) => {\n  console.log('Error:', err)\n})\n```\n```\nError: {\n  code: 'handler_error',\n  message: 'foo is not defined',\n  target: 'page:fail'\n}\n```\n\n## Compatibility\n\nThe package is compatible and tested on both MV2 and MV3 Chrome and Firefox.\n\nAll code written in TypeScript, generated code comes with source maps for easy debugging.\n\n## Support\n\nThis project open sources code from my main project [Control Space](https://controlspace.app/), a super-interactive tab manager for those who juggle **a lot** of tasks:\n\n\n\n[![control space](https://controlspace.app/images/home/examples/actions.png)](https://controlspace.app)\n\n\n\nIf you think Control Space might work for you, click above to find out more and give it a spin.\n\nThanks!\n\nDave\n","readmeFilename":"README.md"}