{"id":27876360,"url":"https://github.com/aaronius/penpal","last_synced_at":"2026-02-13T06:49:25.368Z","repository":{"id":12595411,"uuid":"72321249","full_name":"Aaronius/penpal","owner":"Aaronius","description":"Penpal simplifies communication with iframes, workers, and windows by using promise-based methods on top of postMessage.","archived":false,"fork":false,"pushed_at":"2025-04-01T02:24:08.000Z","size":2113,"stargazers_count":436,"open_issues_count":2,"forks_count":63,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-05-05T02:48:33.670Z","etag":null,"topics":["communication","iframe","iframes","message-channel","message-port","port","postmessage","promise","service-worker","service-workers","shared-worker","shared-workers","web-worker","web-workers","window","windows","workers"],"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/Aaronius.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"Aaronius"}},"created_at":"2016-10-30T02:50:42.000Z","updated_at":"2025-05-04T21:55:51.000Z","dependencies_parsed_at":"2023-12-24T07:49:27.384Z","dependency_job_id":"b0fe84ca-1db8-4101-8c10-270e1f136882","html_url":"https://github.com/Aaronius/penpal","commit_stats":{"total_commits":262,"total_committers":20,"mean_commits":13.1,"dds":0.5267175572519084,"last_synced_commit":"4a1d49f0e0f3a7f198e11772ad82e9d7ea0acff7"},"previous_names":[],"tags_count":76,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aaronius%2Fpenpal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aaronius%2Fpenpal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aaronius%2Fpenpal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aaronius%2Fpenpal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Aaronius","download_url":"https://codeload.github.com/Aaronius/penpal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254092776,"owners_count":22013290,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["communication","iframe","iframes","message-channel","message-port","port","postmessage","promise","service-worker","service-workers","shared-worker","shared-workers","web-worker","web-workers","window","windows","workers"],"created_at":"2025-05-05T02:47:44.663Z","updated_at":"2026-02-13T06:49:25.355Z","avatar_url":"https://github.com/Aaronius.png","language":"TypeScript","readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\".github/assets/readme-banner-white.png\"\u003e\n  \u003csource media=\"(prefers-color-scheme: light)\" srcset=\".github/assets/readme-banner-black.png\"\u003e\n  \u003cimg alt=\"Penpal Logo\" src=\".github/assets/readme-banner-black.png\"\u003e\n\u003c/picture\u003e\n\n\u003c/div\u003e\n\n\u003cp align=\"center\"\u003e\n  Penpal simplifies communication with iframes, workers, and windows by\n  \u003cbr/\u003e\n  using promise-based methods on top of postMessage.\n  \u003cbr/\u003e\n\u003c/p\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![npm version](https://badge.fury.io/js/penpal.svg)](https://badge.fury.io/js/penpal)\n\n\u003c/div\u003e\n\nMigration instructions for each major release can be found on the corresponding GitHub release tag. If you are migrating to v7, [see the v7 release tag for migration instructions](https://github.com/Aaronius/penpal/releases/tag/v7.0.0).\n\n## Installation\n\n### Using npm\n\nInstall Penpal from npm as follows:\n\n`npm install penpal`\n\n### Using a CDN\n\nAlternatively, load a build of Penpal that is already hosted on a CDN:\n\n`\u003cscript src=\"https://unpkg.com/penpal@^7/dist/penpal.min.js\"\u003e\u003c/script\u003e`\n\nPenpal will then be installed on `window.Penpal`. Usage is similar to if you were using it from npm, which is documented below, but instead of importing each module, you would access it on the `Penpal` global variable instead.\n\n## Usage with an Iframe\n\n\u003cdetails\u003e\n    \u003csummary\u003eExpand Details\u003c/summary\u003e\n\n### Parent Window\n\n```javascript\nimport { WindowMessenger, connect } from 'penpal';\n\nconst iframe = document.createElement('iframe');\niframe.src = 'https://childorigin.example.com/path/to/iframe.html';\ndocument.body.appendChild(iframe);\n\nconst messenger = new WindowMessenger({\n  remoteWindow: iframe.contentWindow,\n  // Defaults to the current origin.\n  allowedOrigins: ['https://childorigin.example.com'],\n  // Alternatively,\n  // allowedOrigins: [new Url(iframe.src).origin]\n});\n\nconst connection = connect({\n  messenger,\n  // Methods the parent window is exposing to the iframe window.\n  methods: {\n    add(num1, num2) {\n      return num1 + num2;\n    },\n  },\n});\n\nconst remote = await connection.promise;\n// Calling a remote method will always return a promise.\nconst multiplicationResult = await remote.multiply(2, 6);\nconsole.log(multiplicationResult); // 12\nconst divisionResult = await remote.divide(12, 4);\nconsole.log(divisionResult); // 3\n```\n\n### Iframe Window\n\n```javascript\nimport { WindowMessenger, connect } from 'penpal';\n\nconst messenger = new WindowMessenger({\n  remoteWindow: window.parent,\n  // Defaults to the current origin.\n  allowedOrigins: ['https://parentorigin.example.com'],\n});\n\nconst connection = connect({\n  messenger,\n  // Methods the iframe window is exposing to the parent window.\n  methods: {\n    multiply(num1, num2) {\n      return num1 * num2;\n    },\n    divide(num1, num2) {\n      // Return a promise if asynchronous processing is needed.\n      return new Promise((resolve) =\u003e {\n        setTimeout(() =\u003e {\n          resolve(num1 / num2);\n        }, 1000);\n      });\n    },\n  },\n});\n\nconst remote = await connection.promise;\n// Calling a remote method will always return a promise.\nconst additionResult = await remote.add(2, 6);\nconsole.log(additionResult); // 8\n```\n\n\u003c/details\u003e\n\n## Usage with an Opened Window\n\n\u003cdetails\u003e\n    \u003csummary\u003eExpand Details\u003c/summary\u003e\n\n### Parent Window\n\n```javascript\nimport { WindowMessenger, connect } from 'penpal';\n\nconst windowUrl = 'https://childorigin.example.com/path/to/window.html';\nconst childWindow = window.open(windowUrl);\n\nconst messenger = new WindowMessenger({\n  remoteWindow: childWindow,\n  // Defaults to the current origin.\n  allowedOrigins: ['https://childorigin.example.com'],\n  // Alternatively,\n  // allowedOrigins: [new Url(windowUrl).origin]\n});\n\nconst connection = connect({\n  messenger,\n  // Methods the parent window is exposing to the child window.\n  methods: {\n    add(num1, num2) {\n      return num1 + num2;\n    },\n  },\n});\n\nconst remote = await connection.promise;\n// Calling a remote method will always return a promise.\nconst multiplicationResult = await remote.multiply(2, 6);\nconsole.log(multiplicationResult); // 12\nconst divisionResult = await remote.divide(12, 4);\nconsole.log(divisionResult); // 3\n```\n\n### Opened Window\n\n```javascript\nimport { WindowMessenger, connect } from 'penpal';\n\nconst messenger = new WindowMessenger({\n  remoteWindow: window.opener,\n  // Defaults to the current origin.\n  allowedOrigins: ['https://parentorigin.example.com'],\n});\n\nconst connection = connect({\n  messenger,\n  // Methods the child window is exposing to the parent (opener) window.\n  methods: {\n    multiply(num1, num2) {\n      return num1 * num2;\n    },\n    divide(num1, num2) {\n      // Return a promise if asynchronous processing is needed.\n      return new Promise((resolve) =\u003e {\n        setTimeout(() =\u003e {\n          resolve(num1 / num2);\n        }, 1000);\n      });\n    },\n  },\n});\n\nconst remote = await connection.promise;\n// Calling a remote method will always return a promise.\nconst additionResult = await remote.add(2, 6);\nconsole.log(additionResult); // 8\n```\n\n\u003c/details\u003e\n\n## Usage with a Worker\n\n\u003cdetails\u003e\n    \u003csummary\u003eExpand Details\u003c/summary\u003e\n\n### Window\n\n```javascript\nimport { WorkerMessenger, connect } from 'penpal';\n\nconst worker = new Worker('worker.js');\n\nconst messenger = new WorkerMessenger({\n  worker,\n});\n\nconst connection = connect({\n  messenger,\n  // Methods the window is exposing to the worker.\n  methods: {\n    add(num1, num2) {\n      return num1 + num2;\n    },\n  },\n});\n\nconst remote = await connection.promise;\n// Calling a remote method will always return a promise.\nconst multiplicationResult = await remote.multiply(2, 6);\nconsole.log(multiplicationResult); // 12\nconst divisionResult = await remote.divide(12, 4);\nconsole.log(divisionResult); // 3\n```\n\n### Worker\n\n```javascript\nimport { WorkerMessenger, connect } from 'penpal';\n\nconst messenger = new WorkerMessenger({\n  worker: self,\n});\n\nconst connection = connect({\n  messenger,\n  // Methods the worker is exposing to the window.\n  methods: {\n    multiply(num1, num2) {\n      return num1 * num2;\n    },\n    divide(num1, num2) {\n      // Return a promise if asynchronous processing is needed.\n      return new Promise((resolve) =\u003e {\n        setTimeout(() =\u003e {\n          resolve(num1 / num2);\n        }, 1000);\n      });\n    },\n  },\n});\n\nconst remote = await connection.promise;\n// Calling a remote method will always return a promise.\nconst additionResult = await remote.add(2, 6);\nconsole.log(additionResult); // 8\n```\n\n\u003c/details\u003e\n\n## Usage with a Shared Worker\n\n\u003cdetails\u003e\n    \u003csummary\u003eExpand Details\u003c/summary\u003e\n\n### Window\n\n```javascript\nimport { PortMessenger, connect } from 'penpal';\n\nconst worker = new SharedWorker('shared-worker.js');\n\nconst messenger = new PortMessenger({\n  port: worker.port,\n});\n\nconst connection = connect({\n  messenger,\n  // Methods the window is exposing to the worker.\n  methods: {\n    add(num1, num2) {\n      return num1 + num2;\n    },\n  },\n});\n\nconst remote = await connection.promise;\n// Calling a remote method will always return a promise.\nconst multiplicationResult = await remote.multiply(2, 6);\nconsole.log(multiplicationResult); // 12\nconst divisionResult = await remote.divide(12, 4);\nconsole.log(divisionResult); // 3\n```\n\n### Shared Worker\n\n```javascript\nimport { PortMessenger, connect } from 'penpal';\n\nself.addEventListener('connect', async (event) =\u003e {\n  const [port] = event.ports;\n\n  const messenger = new PortMessenger({\n    port,\n  });\n\n  const connection = connect({\n    messenger,\n    // Methods the worker is exposing to the window.\n    methods: {\n      multiply(num1, num2) {\n        return num1 * num2;\n      },\n      divide(num1, num2) {\n        // Return a promise if asynchronous processing is needed.\n        return new Promise((resolve) =\u003e {\n          setTimeout(() =\u003e {\n            resolve(num1 / num2);\n          }, 1000);\n        });\n      },\n    },\n  });\n\n  const remote = await connection.promise;\n  // Calling a remote method will always return a promise.\n  const additionResult = await remote.add(2, 6);\n  console.log(additionResult); // 8\n});\n```\n\n\u003c/details\u003e\n\n## Usage with a Service Worker\n\n\u003cdetails\u003e\n    \u003csummary\u003eExpand Details\u003c/summary\u003e\n\n### Window\n\n```javascript\nimport { PortMessenger, connect } from 'penpal';\n\nconst initPenpal = async () =\u003e {\n  const { port1, port2 } = new MessageChannel();\n\n  navigator.serviceWorker.controller?.postMessage(\n    {\n      type: 'INIT_PENPAL',\n      port: port2,\n    },\n    {\n      transfer: [port2],\n    }\n  );\n\n  const messenger = new PortMessenger({\n    port: port1,\n  });\n\n  const connection = connect({\n    messenger,\n    // Methods the window is exposing to the worker.\n    methods: {\n      add(num1, num2) {\n        return num1 + num2;\n      },\n    },\n  });\n\n  const remote = await connection.promise;\n  // Calling a remote method will always return a promise.\n  const multiplicationResult = await remote.multiply(2, 6);\n  console.log(multiplicationResult); // 12\n  const divisionResult = await remote.divide(12, 4);\n  console.log(divisionResult); // 3\n};\n\nif (navigator.serviceWorker.controller) {\n  initPenpal();\n}\n\nnavigator.serviceWorker.addEventListener('controllerchange', initPenpal);\nnavigator.serviceWorker.register('service-worker.js');\n```\n\n### Service Worker\n\n```javascript\nimport { PortMessenger, connect } from 'penpal';\n\nself.addEventListener('install', () =\u003e self.skipWaiting());\nself.addEventListener('activate', () =\u003e self.clients.claim());\nself.addEventListener('message', async (event) =\u003e {\n  if (event.data?.type !== 'INIT_PENPAL') {\n    return;\n  }\n\n  const { port } = event.data;\n\n  const messenger = new PortMessenger({\n    port,\n  });\n\n  const connection = connect({\n    messenger,\n    // Methods worker is exposing to window.\n    methods: {\n      multiply(num1, num2) {\n        return num1 * num2;\n      },\n      divide(num1, num2) {\n        // Return a promise if asynchronous processing is needed.\n        return new Promise((resolve) =\u003e {\n          setTimeout(() =\u003e {\n            resolve(num1 / num2);\n          }, 1000);\n        });\n      },\n    },\n  });\n\n  const remote = await connection.promise;\n  // Calling a remote method will always return a promise.\n  const additionResult = await remote.add(2, 6);\n  console.log(additionResult); // 8\n});\n```\n\n\u003c/details\u003e\n\n## Running Examples\n\nIf you'd like to see running examples of the different types of usage, check out the [penpal-sandbox](https://github.com/Aaronius/penpal-sandbox) repository.\n\n## Destroying the Connection\n\nAt any point in time, call `connection.destroy()` to destroy the connection so that event listeners can be removed and objects can be properly garbage collected.\n\n## Debugging\n\nTo debug while using Penpal, specify a function for the `log` option when calling `connect()`. This function will be called whenever Penpal needs to log a message. While this can be any function, Penpal exports a simple logging function called `debug` which you can import and use. Passing a prefix into `debug` will help to distinguish the origin of log messages.\n\n```javascript\nimport { connect, debug } from 'penpal';\n\n...\n\nconst connection = connect({\n  messenger,\n  log: debug('parent')\n});\n```\n\nFor more advanced logging, check out the popular [debug](https://www.npmjs.com/package/debug) package which can be used similarly.\n\n```javascript\nimport debug from 'debug';\nimport { connect } from 'penpal';\n\n...\n\nconst connection = connect({\n  messenger,\n  log: debug('penpal:parent')\n});\n```\n\n## Timeouts\n\n### Connection Timeouts\n\nWhen establishing a connection, you may specify a timeout in milliseconds. If a connection is not successfully made within the timeout period, the connection promise will be rejected with an error. See [Errors](#errors) for more information on errors.\n\n```javascript\nimport { ErrorCode } from 'penpal';\n\n...\n\nconst connection = connect({\n  messenger,\n  timeout: 5000 // 5 seconds\n});\n\ntry {\n  const remote = await connection.promise;\n} catch (error) {\n  if (error.code === ErrorCode.ConnectionTimeout) {\n    // Connection failed due to timeout.\n  }\n}\n```\n\n### Method Call Timeouts\n\nWhen calling a remote method, you may specify a timeout in milliseconds by passing an instance of `CallOptions` as the last argument. If a response is not received within the timeout period, the method call promise will be rejected with an error. See [Errors](#errors) for more information on errors.\n\n```javascript\nimport { CallOptions, ErrorCode } from 'penpal';\n\n...\n\nconst remote = await connection.promise;\n\ntry {\n  const multiplicationResult =\n    await remote.multiply(2, 6, new CallOptions({ timeout: 1000 }));\n} catch (error) {\n  if (error.code === ErrorCode.MethodCallTimeout) {\n    // Method call failed due to timeout.\n  }\n}\n```\n\n## Transferring Large Data\n\nWhen sending a value between windows or workers, the browser uses a [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) by default to _clone_ the value as it is sent. As a result, the value will exist in memory multiple times--once for the sender and once for the recipient. This is typically fine, but some use cases require sending a large amount of data between contexts which could result in a significant performance hit.\n\nTo address this scenario, browsers support [transferable objects](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects) which allow certain types of objects to be _transferred_ between contexts. Rather than cloning the object, the browser will provide the receiving context a pointer to the object's existing block of memory.\n\nWhen calling a remote method using Penpal, you may specify which objects should be transferred rather than cloned by passing an instance of `CallOptions` as the last argument with the `transferables` option set. When responding to a method call, you may specify which objects should be transferred by returning an instance of `Reply` with the `transferables` option set.\n\n### Window\n\n```javascript\nimport { connect, CallOptions } from 'penpal';\n\n...\n\nconst connection = connect({\n  messenger\n});\n\nconst remote = await connection.promise;\n\nconst numbersArray = new Int32Array(new ArrayBuffer(8));\nnumbersArray[0] = 4;\nnumbersArray[1] = 5;\n\nconst multiplicationResultArray = await remote.double(\n  numbersArray,\n  new CallOptions({ transferables: [numbersArray.buffer] })\n);\n\nconsole.log(multiplicationResultArray[0]); // 8\nconsole.log(multiplicationResultArray[1]); // 10\n```\n\n### Worker\n\n```javascript\nimport { connect, Reply } from 'penpal';\n\n...\n\nconst connection = connect({\n  messenger,\n  methods: {\n    double(numbersArray) {\n      // numbersArray and resultArray are both Int32Arrays\n      const resultArray = numbersArray.map(num =\u003e num * 2);\n      return new Reply(resultArray, {\n        transferables: [resultArray.buffer],\n      });\n    }\n  },\n});\n```\n\n## Parallel Connections\n\nIn fairly rare cases, you may wish to make parallel connections between two participants. To illustrate, let's use a scenario where you wish to make two parallel connections between a parent window and an iframe window. In other words, you will be calling `connect()` twice within the parent window and twice within the iframe window.\n\nIn an attempt to establish these two connections, Penpal in the parent window will be calling `postMessage()` on the iframe's window object (`iframe.contentWindow`). By default, when Penpal within the iframe window receives these messages, it has no way to disambiguate messages related to the parent window's first call to `connect()` from messages related to the parent window's second call to `connect()`. As a result, the connections may fail to be properly established.\n\nTo prevent this issue, Penpal provides the concept of channels. A channel is a string identifier of your choosing that you may provide when calling `connect()` within both participants. When a channel is provided, it is used to disambiguate communication between parallel connections. This is better explained in code:\n\n### Parent Window\n\n```javascript\nimport { WindowMessenger, connect } from 'penpal';\n\nconst iframe = document.createElement('iframe');\niframe.src = 'https://childorigin.example.com/iframe.html';\ndocument.body.appendChild(iframe);\n\nconst messengerA = new WindowMessenger({\n  remoteWindow: iframe.contentWindow,\n  allowedOrigins: ['https://childorigin.example.com'],\n});\n\nconst connectionA = connect({\n  messenger: messengerA,\n  channel: 'A',\n  methods: {\n    add(num1, num2) {\n      return num1 + num2;\n    },\n  },\n});\n\n// Note that each call to connect() needs a separate messenger instance.\nconst messengerB = new WindowMessenger({\n  remoteWindow: iframe.contentWindow,\n  allowedOrigins: ['https://childorigin.example.com'],\n});\n\nconst connectionB = connect({\n  messenger: messengerB,\n  channel: 'B',\n  methods: {\n    subtract(num1, num2) {\n      return num1 - num2;\n    },\n  },\n});\n```\n\n### Iframe Window\n\n```javascript\nimport { WindowMessenger, connect } from 'penpal';\n\nconst messengerA = new WindowMessenger({\n  remoteWindow: window.parent,\n  allowedOrigins: ['https://parentorigin.example.com'],\n});\n\nconst connectionA = connect({\n  messenger: messengerA,\n  channel: 'A',\n  methods: {\n    multiply(num1, num2) {\n      return num1 * num2;\n    },\n  },\n});\n\n// Note that each call to connect() needs a separate messenger instance.\nconst messengerB = new WindowMessenger({\n  remoteWindow: iframe.contentWindow,\n  allowedOrigins: ['https://parentorigin.example.com'],\n});\n\nconst connectionB = connect({\n  messenger: messengerB,\n  channel: 'B',\n  methods: {\n    divide(num1, num2) {\n      return num1 / num2;\n    },\n  },\n});\n```\n\nAlthough we're using `WindowMessenger` here to connect between a parent window and an iframe window, channels would similarly need to be used when using `WorkerMessenger` to make parallel connections to a worker. When using `PortMessenger`, channels are only needed when establishing parallel connections over a single pair of ports.\n\n## Errors\n\nPenpal will throw or reject promises with errors in certain situations. Each error will be an instance of `PenpalError` and will have a `code` property which may be used for programmatic decisioning (e.g., take a specific action if a method call times out) along with a `message` describing the problem. Changes to error codes will be considered breaking changes and require a new major version of Penpal to be released. Changes to messages will not be considered breaking changes. The following error codes are used:\n\n`CONNECTION_DESTROYED`\n\nThis error will be thrown when attempting to call a method and the connection was previously destroyed.\n\n`CONNECTION_TIMEOUT`\n\nThe promise found at `connection.promise` will be rejected with this error after the configured [connection timeout](#connection-timeouts) duration has elapsed and a connection has not been established.\n\n`INVALID_ARGUMENT`\n\nThis error will be thrown when an invalid argument is passed to Penpal.\n\n`METHOD_CALL_TIMEOUT`\n\nThe promise returned from a method call will be rejected with this error after the configured [method call timeout](#method-call-timeouts) duration has elapsed and a response has not been received.\n\n`METHOD_NOT_FOUND`\n\nThe promise returned from a method call will be rejected with this error if the method does not exist on the remote.\n\n`TRANSMISSION_FAILED`\n\nWhen a connection is being established, the promise found at `connection.promise` will be rejected with this error if a message cannot be transmitted. When a method call is being made, the promise returned from the method call will be rejected with this error if a message cannot be transmitted.\n\n### Referencing Error Codes\n\nFor your convenience, the above error codes can be imported and referenced as follows:\n\n```\nimport { ErrorCode } from 'penpal';\n// ErrorCode.ConnectionDestroyed\n// ErrorCode.ConnectionTimeout\n// ErrorCode.InvalidArgument\n// ErrorCode.MethodCallTimeout\n// ErrorCode.MethodNotFound\n// ErrorCode.TransmissionFailed\n```\n\n## TypeScript\n\nPenpal is built in TypeScript and provides full TypeScript support. When calling `connect()`, it's recommended you pass a generic type argument that describes the methods the remote will be exposing. This will be used to type the `remote` object that `connection.promise` is resolved with. This is better explained in code:\n\n### Window Connecting to a Worker\n\n```typescript\nimport { WorkerMessenger, connect } from 'penpal';\n\n// This interace could be in a module imported by both the window and worker.\ninterface WorkerApi {\n  multiply(...args: number[]): number;\n}\n\nconst worker = new Worker('worker.js');\n\nconst messenger = new WorkerMessenger({\n  worker,\n});\n\n// Note we're passing in WorkerApi as a generic type argument.\nconst connection = connect\u003cWorkerApi\u003e({\n  messenger,\n});\n\n// This `remote` object will contain properly typed methods.\nconst remote = await connection.promise;\n// This `multiplicationResult` constant will be properly typed as a number.\nconst multiplicationResult = await remote.multiply(2, 6);\n```\n\nWhen creating a worker, it's highly recommended that you add the following line of code at the top of your worker script depending on which type of worker you're creating:\n\n- Dedicated (regular) worker: `declare const self: DedicatedWorkerGlobalScope;`\n- Shared worker: `declare const self: SharedWorkerGlobalScope;`\n- Service worker: `declare const self: ServiceWorkerGlobalScope;`\n\nThis lets TypeScript know which type of worker you're creating, and you'll run into fewer TypeScript errors.\n\n### Exported Types\n\nPenpal exports several types for your usage. Import types as follows:\n\n```typescript\nimport { Connection, Methods, RemoteProxy } from 'penpal';\n```\n\nThe types are described as follows:\n\n#### `Connection`\n\nThe connection object returned from `connect()` is typed as `Connection`.\n\n#### `Methods`\n\nThe object you provide for the `methods` option when calling `connect()` must be compatible with the `Methods` type. The generic type argument you pass when calling `connect()` must also be compatible with the `Methods` type.\n\n#### `RemoteProxy`\n\nThe object that `connection.promise` resolves to will be of type `RemoteProxy`. More specifically, it will be of type `RemoteProxy\u003cTMethods\u003e`, where `TMethods` is the type you pass as a generic type argument when calling `connect()` as described above.\n\n## React\n\nIf you're using Penpal within a React app, please check out [@weblivion/react-penpal](https://www.npmjs.com/package/@weblivion/react-penpal).\n\n## Supported Browsers\n\nPenpal is designed to run successfully on the most recent versions of Chrome, Firefox, Safari, and Edge. Penpal has also been reported to work within Ionic projects on iOS and Android devices.\n\n## API\n\n### `connect(options: Object) =\u003e Object`\n\n#### Options\n\n`messenger: Messenger` (required)\n\nA messenger instance. Messengers handle the technical details of transmitting messages. The current available messengers are `WindowMessenger`, `WorkerMessenger`, and `PortMessenger`, though any object that complies with the Messenger interface may be used. Details related to building custom messengers are forthcoming.\n\n`methods: Object` (optional)\n\nAn object containing methods which should be exposed for the remote to call. The keys of the object are the method names and the values are the functions. Nested objects with function values are recursively included. If a function requires asynchronous processing to determine its return value, make the function immediately return a promise and resolve the promise once the value has been determined.\n\n`timeout: number` (optional)\n\nThe amount of time, in milliseconds, Penpal should wait for a connection to be established before rejecting the connection promise. There is no timeout by default. See [Connection Timeouts](#connection-timeouts) for more information.\n\n`channel: string` (optional)\n\nA string identifier that disambiguates communication when establishing parallel connections between two participants (e.g., two windows, a window and a worker). See [Parallel Connections](#parallel-connections) for more information.\n\n`log: (...args: unknown[]) =\u003e void` (optional)\n\nPenpal will call the log function each time debugging information is available. Debug messages will only be logged when this is defined. See [Debugging](#debugging) for more information.\n\n#### Return value\n\nThe return value of `connect` is a `Connection` object with the following properties:\n\n`promise: Promise`\n\nA promise which will be resolved once communication has been established. The promise will be resolved with an object that serves as a proxy for the methods the remote has exposed. Calling a method on this proxy object will always return a promise since it involves sending messages to and from the remote which are asynchronous operations. When calling a method on this proxy object, you may always pass an instance of `CallOptions` as a final argument. See [Method Call Timeouts](#method-call-timeouts) and [Transferring Large Data](#transferring-large-data) for more information on `CallOptions`.\n\n`destroy: () =\u003e void`\n\nA method that, when called, will disconnect any messaging channels, event listeners, etc. You may call this even before a connection has been established. See [Destroying the Connection](#destroying-the-connection) for more information.\n\n---\n\n### `WindowMessenger`\n\nThis messenger supports communication between two windows. See [Usage with an Iframe](#usage-with-an-iframe) and [Usage with an Opened Window](#usage-with-an-opened-window) for examples.\n\n#### Constructor Options\n\n`remoteWindow: Window`\n\nA reference to the remote window exposing methods to the current (local) window. When connecting between a parent window and a child iframe window, in the parent you would specify the iframe's content window (`iframe.contentWindow`) while in the child you would specify the parent window (`window.parent`). When connecting between a parent window and a \"child\" window opened using `window.open()`, in the parent would specify the opened window while in the child you would specify the parent (opener) window (`window.opener`).\n\n`allowedOrigins: (string | RegExp)[]` (optional)\n\nAn array of allowed origins with which the window is allowed to communicate. By default, Penpal will restrict communication to the origin of the current HTML document.\n\nIn some [scenarios](https://github.com/Aaronius/penpal/issues/73), you may want the window to communicate with any origin. In this case, you can specify `*` as an allowed origin. **This is discouraged as it means any website could potentially send or receive method calls.** When using the `file://` protocol or data URIs when loading HTML documents, you will likely need to specify `*` as an allowed origin due to native browser security policies but, again, understand your risks in doing so.\n\nRegardless of how you configure `allowedOrigins`, communication will always be restricted to the window with which you are connecting.\n\n---\n\n### `WorkerMessenger`\n\nThis messenger supports communication with a [Worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker) (also known as a dedicated worker). See [Usage with a Worker](#usage-with-a-worker) for an example. This cannot be used for shared workers or service workers, which use the [PortMessenger](#portmessenger) instead.\n\n#### Constructor Options\n\n`worker: Worker | DedicatedWorkerGlobalScope`\n\nA reference to the worker. When connecting from a window, you would specify the instantiated worker object. When connecting from the worker, you would specify `self`.\n\n---\n\n### `PortMessenger`\n\nThis messenger supports communication between a pair of [MessagePorts](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort). This is particularly useful when establishing a connection with a [SharedWorker](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) or [ServiceWorker](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker). See [Usage with a Shared Worker](#usage-with-a-shared-worker) and [Usage with a Service Worker](#usage-with-a-service-worker) for examples.\n\n#### Constructor Options\n\n`port: MessagePort`\n\nA reference to the port. Each of the two participants in a Penpal connection will have its own [MessagePort](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort).\n\n---\n\n## Documentation for Previous Versions\n\n- [v6 documentation](https://github.com/Aaronius/penpal/tree/6.x)\n- [v5 documentation](https://github.com/Aaronius/penpal/tree/5.x)\n- [v4 documentation](https://github.com/Aaronius/penpal/tree/4.x)\n- [v3 documentation](https://github.com/Aaronius/penpal/tree/3.x)\n\n## Inspiration\n\nThis library is inspired by:\n\n- [Postmate](https://github.com/dollarshaveclub/postmate)\n- [JSChannel](https://github.com/mozilla/jschannel)\n- [post-me](https://github.com/alesgenova/post-me)\n- [Comlink](https://github.com/GoogleChromeLabs/comlink)\n\n## License\n\nMIT\n","funding_links":["https://github.com/sponsors/Aaronius"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faaronius%2Fpenpal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faaronius%2Fpenpal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faaronius%2Fpenpal/lists"}