{"_id":"@arconnect/webext-bridge","_rev":"2-d0bafea3a8c9a7c2c631d8cea6b1928d","name":"@arconnect/webext-bridge","dist-tags":{"latest":"5.0.6"},"versions":{"5.0.6":{"name":"@arconnect/webext-bridge","version":"5.0.6","keywords":["chrome","extension","messaging","communication","protocol","content","background","devtools","script","crx","bridge"],"license":"MIT","_id":"@arconnect/webext-bridge@5.0.6","maintainers":[{"name":"martondev","email":"martondeveloper@gmail.com"}],"dist":{"shasum":"edcc28d0d7e215fba2e5a3c832cf7842f7046b9d","tarball":"https://registry.npmjs.org/@arconnect/webext-bridge/-/webext-bridge-5.0.6.tgz","fileCount":8,"integrity":"sha512-Zxflrgmb5iZzpqUEqVc+FkfHnW18txORS5ERLLyeeE+2j7sNVmmdjP2tbW7ypmeyyHZ5yu4WBJP6AHOuf5AoSg==","signatures":[{"sig":"MEUCIQCKJIEa4q0IK9rl0KzheICWCnlFCNpzHFjomB1xQqNP8gIgGC69cRY329T1UEog71L7eZAUYRjzSnI8IINQPuaIBy0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":56545,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjMCTjACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmreMA/5AWKI4y1rLBX9sEGeV30N0velkca5pkL1WV+cDjkOZtzMPhz9\r\n8MmuoJ3sUOAeo++o/duNBCZ61tnaL5Aoz+dCzK799OoSeWukNJAoAiU1hjjR\r\nOf91dz03UIwqsA9LIq5Q5xCQIeRvtqhoc+bLrNXmUGBi4siSyH/8rmQZ/n++\r\nnAZwMSk6KWnOFAIqsvmHt83fsqKQblWYpUgSKS/BO1APsyS02AGkNW3rjY/l\r\nqos24cNQfchh91K8opOMOrtfbenq9Aj61Fl80u6OlQsl8ashUh8Xy+hqBLka\r\nbQ1a1jSI//6+rO4qOZy6Cuv/I2DeXG/wWF+fIIQ0Ykiu09bxz/5n1OlOJKWZ\r\nHGsePI9FtcmgcZLbs4K9iuPP27yZZ9AQ6S9ClBmgVUmX2nT/azBoB2G60c4y\r\nvpWd17WeUC2iOtGXdTXlDm07ZHboobFenY8qrWkuukoLMB8uCBq4hpw2ywG+\r\nZoQWS1dMRxOCUBIUhzg9KUGWoHe7PK5DiFf8hEgDx9G5500BrqAsFgSDFFOo\r\n8lr8cWr4+qWIdlnSuHaSIZFqhgQOiC9I8OzkcNb5pxSnk2Ni6/QJdY/simIG\r\neyhatY07nOi/MqPYb2N1XGCGx41iYy6bujt8QJNXSUt7iCZ1kg2r1PoV0eU5\r\npGqI5uacqq2mmO13nppYjqZXw0UjcVm6t88=\r\n=tuAu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","scripts":{"build":"tsup src/index.ts --format esm,cjs --dts","watch":"npm run build -- --watch","release":"bumpp --commit --push --tag && npm run build && npm publish"},"_npmUser":{"name":"martondev","email":"martondeveloper@gmail.com"},"repository":{"url":"git+https://github.com/arconnectio/webext-bridge.git","type":"git"},"description":"Messaging in Web Extensions made easy. Out of the box.","directories":{},"licenseText":"MIT License\n\nCopyright (c) 2017 Neek Sandhu\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","dependencies":{"tiny-uid":"^1.1.1","nanoevents":"^6.0.2","serialize-error":"^9.0.0","webextension-polyfill":"^0.9.0","@types/webextension-polyfill":"^0.8.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^5.11.13","bumpp":"^7.1.1","eslint":"^8.8.0","type-fest":"^2.11.1","typescript":"^4.5.5","@types/node":"^17.0.16","@antfu/eslint-config":"^0.16.1","@typescript-eslint/parser":"^5.11.0","@typescript-eslint/eslint-plugin":"^5.11.0"},"_npmOperationalInternal":{"tmp":"tmp/webext-bridge_5.0.6_1664099554803_0.10608734577636292","host":"s3://npm-registry-packages"}}},"time":{"created":"2022-09-25T09:52:34.731Z","modified":"2024-11-23T22:38:38.105Z","5.0.6":"2022-09-25T09:52:34.984Z"},"license":"MIT","keywords":["chrome","extension","messaging","communication","protocol","content","background","devtools","script","crx","bridge"],"repository":{"url":"git+https://github.com/arconnectio/webext-bridge.git","type":"git"},"description":"Messaging in Web Extensions made easy. Out of the box.","maintainers":[{"email":"martondeveloper@gmail.com","name":"martondev"},{"email":"nma@communitylabs.com","name":"nickeeeeyyyy"},{"email":"mparij@gmail.com","name":"7i7o"}],"readme":"# webext-bridge\n\nMessaging in WebExtension made super easy. Out of the box.\n\n[![](https://img.shields.io/npm/v/webext-bridge?color=2B90B6&label=)](https://www.npmjs.com/package/webext-bridge)\n\n> Forked from [crx-bridge](https://github.com/NeekSandhu/crx-bridge) by [NeekSandhu](https://github.com/NeekSandhu)\n\n##### Changes in this Fork\n\n- build esm instead of cjs (for better bundler optimization)\n- use `nanoevents` instead of `events` (decoupled form node module)\n- [type safe protocols](#type-safe-protocols)\n\n----\n\n## How much easy exactly?\n\nThis much\n\n<a name=\"example\"></a>\n\n```javascript\n// Inside devtools script\nimport { sendMessage } from 'webext-bridge'\n\n// ...\n\nbutton.addEventListener('click', async () => {\n  const res = await sendMessage('get-selection',  { ignoreCasing: true },  'content-script')  \n  console.log(res)   // > \"The brown fox is alive and well\"\n})\n```\n\n```javascript\n// Inside content script\nimport { sendMessage, onMessage } from 'webext-bridge'  \n\nonMessage('get-selection', async (message) => {\n  const { sender, data: { ignoreCasing } } = message  \n\n  console.log(sender.context, sender.tabId)   // > content-script  156\n\n  const { selection } = await sendMessage('get-preferences', { sync: false }, 'background')  \n  return calculateSelection(data.ignoreCasing, selection)  \n})  \n```\n\n```javascript\n// Inside background script\nimport { onMessage } from 'webext-bridge'  \n\nonMessage('get-preferences', ({ data }) => {\n  const { sync } = data  \n\n  return loadUserPreferences(sync)  \n})  \n```\n\n> Examples above require transpilation and/or bundling using `webpack`/`babel`/`rollup`\n\n`webext-bridge` handles everything for you as efficiently as possible. No more `chrome.runtime.sendMessage` or `chrome.runtime.onConnect` or `chrome.runtime.connect` ....\n\n## Setup\n\n### Install\n\n```bash\n$ npm i webext-bridge\n```\n\n#### Light it up\n\nJust `import { } from 'webext-bridge'` wherever you need it and use as shown in [example above](#example)\n\n> Even if your extension doesn't need a background page or wont be sending/receiving messages in background script.\n> <br> `webext-bridge` uses background/event context as staging area for messages, therefore it **must** loaded in background/event page for it to work.\n> <br> (Attempting to send message from any context will fail silently if `webext-bridge` isn't available in background page).\n> <br> See [troubleshooting section](#troubleshooting) for more.\n\n<a name=\"api\"></a>\n\n## Type Safe Protocols\n\nAs we are likely to use `sendMessage` and `onMessage` in different context, keep the type consistent could be hard and easy to make mistakes. `webext-bridge` provide a smarter way to make the type for protocols much easier.\n\nCreate `shim.d.ts` file with the following content and make sure it's been included in `tsconfig.json`.\n\n```ts\n// shim.d.ts\nimport { ProtocolWithReturn } from 'webext-bridge'\n\ndeclare module 'webext-bridge' {\n  export interface ProtocolMap {\n    foo: { title: string }\n    // to specify the return type of the message,\n    // use the `ProtocolWithReturn` type wrapper\n    bar: ProtocolWithReturn<CustomDataType, CustomReturnType>\n  }\n}\n```\n\n```ts\nimport { onMessage } from 'webext-bridge'\n\nonMessage('foo', ({ data }) => {\n  // type of `data` will be `{ title: string }`\n  console.log(data.title)\n}\n```\n\n```ts\nimport { sendMessage } from 'webext-bridge'\n\nconst returnData = await sendMessage('bar', { /* ... */ })\n// type of `returnData` will be `CustomReturnType` as specified\n```\n\n## API\n\n### `sendMessage(messageId: string, data: any, destination: string)`\n\nSends a message to some other part of your extension, out of the box.\n\nNotes:\n\n- If there is no listener on the other side an error will be thrown where `sendMessage` was called.\n\n- Listener on the other may want to reply. Get the reply by `await`ing the returned `Promise`\n\n- An error thrown in listener callback (in the destination context) will behave as usual, that is, bubble up, but the same error will also be thrown where `sendMessage` was called\n\n##### `messageId`\n\n> Required | `string`\n\nAny `string` that both sides of your extension agree on. Could be `get-flag-count` or `getFlagCount`, as long as it's same on receiver's `onMessage` listener.\n\n##### `data`\n\n> Required | `any`\n\nAny serializable value you want to pass to other side, latter can access this value by refering to `data` property of first argument to `onMessage` callback function.\n\n##### `destination`\n\n> Required | `string | ` \n\nThe actual identifier of other endpoint.\nExample: `devtools` or `content-script` or `background` or `content-script@133` or `devtools@453` or `web_accessible@3412`\n\n`content-script`, `window`, `devtools` and `web_accessible` destinations can be suffixed with `@<tabId>` to target specific tab. Example: `devtools@351`, points to devtools panel inspecting tab with id 351.\n\nFor `content-script`, a specific `frameId` can be specified by appending the `frameId` to the suffix `@<tabId>.<frameId>`.\n\nRead `Behavior` section to see how destinations (or endpoints) are treated.\n\n> Note: For security reasons, if you want to receive or send messages to or from `window` context, one of your extension's content script must call `allowWindowMessaging(<namespace: string>)` to unlock message routing. Also call `setNamespace(<namespace: string>)` in those `window` contexts. Use same namespace string in those two calls, so `webext-bridge` knows which message belongs to which extension (in case multiple extensions are using `webext-bridge` in one page)\n\n---\n\n### `onMessage(messageId: string, callback: fn)`\n\nRegister one and only one listener, per messageId per context. That will be called upon `sendMessage` from other side.\n\nOptionally, send a response to sender by returning any value or if async a `Promise`.\n\n##### `messageId`\n\n> Required | `string`\n\nAny `string` that both sides of your extension agree on. Could be `get-flag-count` or `getFlagCount`, as long as it's same in sender's `sendMessage` call.\n\n##### `callback`\n\n> Required | `fn`\n\nA callback function `Bridge` should call when a message is received with same `messageId`. The callback function will be called with one argument, a `BridgeMessage` which has `sender`, `data` and `timestamp` as its properties.\n\nOptionally, this callback can return a value or a `Promise`, resolved value will sent as reply to sender.\n\nRead [security note](#security) before using this.\n\n---\n\n### `allowWindowMessaging(namespace: string)`\n\n> Caution: Dangerous action\n\nApplicable to content scripts (noop if called from anywhere else)\n\nUnlocks the transmission of messages to and from `window` (top frame of loaded page) contexts in the tab where it is called.\n`webext-bridge` by default won't transmit any payload to or from `window` contexts for security reasons.\nThis method can be called from a content script (in top frame of tab), which opens a gateway for messages.\n\nOnce again, `window` = the top frame of any tab. That means **allowing window messaging without checking origin first** will let JavaScript loaded at `https://evil.com` talk with your extension and possibly give indirect access to things you won't want to, like `history` API. You're expected to ensure the\nsafety and privacy of your extension's users.\n\n##### `namespace`\n\n> Required | `string`\n\nCan be a domain name reversed like `com.github.facebook.react_devtools` or any `uuid`. Call `setNamespace` in `window` context with same value, so that `webext-bridge` knows which payload belongs to which extension (in case there are other extensions using `webext-bridge` in a tab). Make sure namespace string is unique enough to ensure no collisions happen.\n\n---\n\n### `setNamespace(namespace: string)`\n\nApplicable to scripts in top frame of loaded remote page\n\nSets the namespace `Bridge` should use when relaying messages to and from `window` context. In a sense, it connects the callee context to the extension which called `allowWindowMessaging(<namespace: string>)` in it's content script with same namespace.\n\n##### `namespace`\n\n> Required | `string`\n\nCan be a domain name reversed like `com.github.facebook.react_devtools` or any `uuid`. Call `setNamespace` in `window` context with same value, so that `webext-bridge` knows which payload belongs to which extension (in case there are other extensions using `webext-bridge` in a tab). Make sure namespace string is unique enough to ensure no collisions happen.\n\n### Extras\n\nThe following API is built on top of `sendMessage` and `onMessage`, basically, it's just a wrapper, the routing and security rules still apply the same way.\n\n#### `openStream(channel: string, destination: string)`\n\nOpens a `Stream` between caller and destination.\n\nReturns a `Promise` which resolves with `Stream` when the destination is ready (loaded and `onOpenStreamChannel` callback registered).\nExample below illustrates a use case for `Stream`\n\n##### `channel`\n\n> Required | `string`\n\n`Stream`(s) are strictly scoped `sendMessage`(s). Scopes could be different features of your extension that need to talk to the other side, and those scopes are named using a channel id.\n\n##### `destination`\n\n> Required | `string`\n\nSame as `destination` in `sendMessage(msgId, data, destination)`\n\n---\n\n#### `onOpenStreamChannel(channel: string, callback: fn)`\n\nRegisters a listener for when a `Stream` opens.\nOnly one listener per channel per context\n\n##### `channel`\n\n> Required | `string`\n\n`Stream`(s) are strictly scoped `sendMessage`(s). Scopes could be different features of your extension that need to talk to the other side, and those scopes are named using a channel id.\n\n##### `callback`\n\n> Required | `fn`\n\nCallback that should be called whenever `Stream` is opened from the other side. Callback will be called with one argument, the `Stream` object, documented below.\n\n`Stream`(s) can be opened by a malicious webpage(s) if your extension's content script in that tab has called `allowWindowMessaging`, if working with sensitive information use `isInternalEndpoint(stream.info.endpoint)` to check, if `false` call `stream.close()` immediately.\n\n##### Stream Example\n\n```javascript\n// background.js\n\n// To-Do\n```\n\n<a name=\"behaviour\"></a>\n\n## Behavior\n\n> Following rules apply to `destination` being specified in `sendMessage(msgId, data, destination)` and `openStream(channelId, initialData, destination)`\n\n- Specifying `devtools` as destination from `content-script` will auto-route payload to inspecting `devtools` page if open and listening.\n\n- Specifying `content-script` as destination from `devtools` will auto-route the message to inspected window's top `content-script` page if listening. If page is loading, message will be queued up and delivered when page is ready and listening.\n\n- If `window` context (which could be a script injected by content script) are source or destination of any payload, transmission must be first unlocked by calling `allowWindowMessaging(<namespace: string>)` inside that page's top content script, since `Bridge` will first deliver the payload to `content-script` using rules above, and latter will take over and forward accordingly. `content-script` <-> `window` messaging happens using `window.postMessage` API. Therefore to avoid conflicts, `Bridge` requires you to call `setNamespace(uuidOrReverseDomain)` inside the said window script (injected or remote, doesn't matter).\n\n- Specifying `devtools` or `content-script` or `window` from `background` will throw an error. When calling from `background`, destination must be suffixed with tab id. Like `devtools@745` for `devtools` inspecting tab id 745 or `content-script@351` for top `content-script` at tab id 351.\n\n<a name=\"security\"></a>\n\n## Serious security note\n\nThe following note only applies if and only if, you will be sending/receiving messages to/from `window` contexts. There's no security concern if you will be only working with `content-script`, `background` or `devtools` scope, which is default setting.\n\n`window` context(s) in tab `A` get unlocked the moment you call `allowWindowMessaging(namespace)` somewhere in your extension's content script(s) that's also loaded in tab `A`.\n\nUnlike `chrome.runtime.sendMessage` and `chrome.runtime.connect`, which requires extension's manifest to specify sites allowed to talk with the extension, `webext-bridge` has no such measure by design, which means any webpage whether you intended or not, can do `sendMessage(msgId, data, 'background')` or something similar that produces same effect, as long as it uses same protocol used by `webext-bridge` and namespace set to same as yours.\n\nSo to be safe, if you will be interacting with `window` contexts, treat `webext-bridge` as you would treat `window.postMessage` API.\n\nBefore you call `allowWindowMessaging`, check if that page's `window.location.origin` is something you expect already.\n\nAs an example if you plan on having something critical, **always** verify the `sender` before responding:\n\n```javascript\n// background.js\nimport { onMessage, isInternalEndpoint } from 'webext-bridge'  \n\nonMessage('getUserBrowsingHistory', (message) => {\n  const { data, sender } = message  \n  // Respond only if request is from 'devtools', 'content-script' or 'background' endpoint\n  if (isInternalEndpoint(sender)) {\n    const { range } = data  \n    return getHistory(range)  \n  }\n})  \n```\n\n<a name=\"troubleshooting\"></a>\n\n## Troubleshooting\n\n- Doesn't work?\n  <br>If `window` contexts are not part of the puzzle, `webext-bridge` works out of the box for messaging between `devtools` <-> `background` <-> `content-script`(s). If even that is not working, it's likely that `webext-bridge` hasn't been loaded in background page of your extension, which is used by `webext-bridge` as a staging area. If you don't need a background page for yourself, here's bare minimum to get `webext-bridge` going.\n\n```javascript\n// background.js (requires transpiration/bundling using webpack(recommended))\n\nimport 'webext-bridge'  \n```\n\n```javascript\n// manifest.json\n\n{\n  \"background\": {\n    \"scripts\": [\"path/to/transpiled/background.js\"]\n  }\n}\n```\n\n- Can't send messages to `window`?\n  <br>Sending or receiving messages from or to `window` requires you to open the messaging gateway in content script(s) for that particular tab. Call `allowWindowMessaging(<namespaceA: string>)` in any of your content script(s) in that tab and call `setNamespace(<namespaceB: string>)` in the\n  script loaded in top frame i.e the `window` context. Make sure that `namespaceA === namespaceB`. If you're doing this, read the [security note above](#security)\n","readmeFilename":"README.md"}