{"id":13469664,"url":"https://github.com/alesgenova/post-me","last_synced_at":"2025-05-16T16:04:56.899Z","repository":{"id":43670151,"uuid":"316850315","full_name":"alesgenova/post-me","owner":"alesgenova","description":"📩 Use web Workers and other Windows through a simple Promise API","archived":false,"fork":false,"pushed_at":"2021-02-19T02:26:52.000Z","size":820,"stargazers_count":513,"open_issues_count":8,"forks_count":13,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-05-13T19:03:12.262Z","etag":null,"topics":["communication","concurrency","iframe","javascript","parallel-computing","postmate","postmessage","promise","typescript","web-worker","webworker","worker"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/post-me","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/alesgenova.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-11-29T00:54:17.000Z","updated_at":"2025-05-12T22:35:00.000Z","dependencies_parsed_at":"2022-09-02T07:10:37.540Z","dependency_job_id":null,"html_url":"https://github.com/alesgenova/post-me","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alesgenova%2Fpost-me","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alesgenova%2Fpost-me/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alesgenova%2Fpost-me/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alesgenova%2Fpost-me/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alesgenova","download_url":"https://codeload.github.com/alesgenova/post-me/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254564121,"owners_count":22092121,"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","concurrency","iframe","javascript","parallel-computing","postmate","postmessage","promise","typescript","web-worker","webworker","worker"],"created_at":"2024-07-31T15:01:49.516Z","updated_at":"2025-05-16T16:04:56.661Z","avatar_url":"https://github.com/alesgenova.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"[![workflow status](https://github.com/alesgenova/post-me/workflows/main/badge.svg?branch=main)](https://github.com/alesgenova/post-me/actions?query=workflow%3Amain+branch%3Amain)\n[![npm package](https://img.shields.io/npm/v/post-me.svg)](https://www.npmjs.com/package/post-me)\n[![codecov](https://codecov.io/gh/alesgenova/post-me/branch/main/graph/badge.svg)](https://codecov.io/gh/alesgenova/post-me)\n\n\u003ch1 align=\"center\"\u003epost-me\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003eCommunicate with web \u003ccode\u003eWorkers\u003c/code\u003e and other \u003ccode\u003eWindows\u003c/code\u003e using a simple \u003ccode\u003ePromise\u003c/code\u003e based API\u003c/p\u003e\n\n![diagram](./diagram.png)\n\nWith __post-me__ it is easy for a parent (for example the main app) and a child (for example a worker or an iframe) to expose methods and custom events to each other.\n\n## Features\n- 🔁 Parent and child can both __expose__ __methods__ and/or __events__.\n- 🔎 __Strong typing__ of method names, arguments, return values, as well as event names and payloads.\n- 🤙 Seamlessly pass __callbacks__ to the other context to get progress or partial results.\n- 📨 __Transfer__ arguments/return values/payloads when needed instead of cloning.\n- 🔗 Establish __multiple__ concurrent __connections__.\n- 🌱 __No dependencies__: 2kb gzip bundle.\n- 🧪 Excellent __test coverage__.\n- 👐 Open source (MIT)\n\n## Demo\nIn this [live demo](https://alesgenova.github.io/post-me-demo) the main window communicates with a web worker and an iframe ([source](https://github.com/alesgenova/post-me-demo)).\n\n## Content:\n1. [Install](#install)\n2. [Basic Usage](#usage)\n3. [Typescript Support](#typescript)\n4. [Other Uses](#other)\n    - [Windows](#windows)\n    - [MessageChannels](#channels)\n5. [Callbacks as parameters](#callbacks)\n6. [Transfer vs Clone](#transfer)\n7. [Debugging](#debugging)\n8. [Parallel Programming](#parallel)\n9. [API Documentation](#api)\n10. [References](#references)\n\n\u003ca id=\"install\"\u003e\u003c/a\u003e\n\n## Install\nImport __post-me__ as a module:\n```bash\nnpm install post-me\n```\n```typescript\nimport { ParentHandshake } from 'post-me';\n```\n\nImport __post-me__ as a script:\n```html\n\u003cscript src=\"https://unpkg.com/post-me/dist/index.js\"\u003e\u003c/script\u003e\n\n\u003cscript\u003e\n  const ParentHandshake = PostMe.ParentHandshake;\n\u003c/script\u003e\n```\n\n\u003ca id=\"usage\"\u003e\u003c/a\u003e\n\n## Usage\nIn the example below, the parent application calls methods exposed by the worker and listens to events emitted by it.\n\nFor the sake of simiplicity, only the worker is exposing methods and events, however the parent could do it as well.\n\nParent code:\n```typescript\nimport { ParentHandshake, WorkerMessenger } from 'post-me';\n\nconst worker = new Worker('./worker.js');\n\nconst messenger = new WorkerMessenger({ worker });\n\nParentHandshake(messenger).then((connection) =\u003e {\n  const remoteHandle = connection.remoteHandle();\n\n  // Call methods on the worker and get the result as a promise\n  remoteHandle.call('sum', 3, 4).then((result) =\u003e {\n    console.log(result); // 7\n  });\n\n  // Listen for a specific custom event from the worker\n  remoteHandle.addEventListener('ping', (payload) =\u003e {\n    console.log(payload) // 'Oh, hi!'\n  });\n});\n```\n\nWorker code:\n```typescript\nimport { ChildHandshake, WorkerMessenger } from 'post-me';\n\n// Methods exposed by the worker: each function can either return a value or a Promise.\nconst methods = {\n  sum: (x, y) =\u003e x + y,\n  mul: (x, y) =\u003e x * y\n}\n\nconst messenger = WorkerMessenger({worker: self});\nChildHandshake(messenger, methods).then((connection) =\u003e {\n  const localHandle = connection.localHandle();\n\n  // Emit custom events to the app\n  localHandle.emit('ping',  'Oh, hi!');\n});\n```\n\n\u003ca id=\"typescript\"\u003e\u003c/a\u003e\n\n## Typescript\nUsing typescript you can ensure that the parent and the child are using each other's methods and events correctly. Most coding mistakes will be caught during development by the typescript compiler.\n\nThanks to __post-me__ extensive typescript support, the correctness of the following items can be statically checked during development:\n- Method names\n- Argument number and types\n- Return values type\n- Event names\n- Event payload type\n\nBelow a modified version of the previous example using typescript.\n\nTypes code:\n```typescript\n// types.ts\n\nexport type WorkerMethods = {\n  sum: (x: number, y: number) =\u003e number;\n  mul: (x: number, y: number) =\u003e number;\n}\n\nexport type WorkerEvents = {\n  'ping': string;\n}\n```\n\nParent Code:\n```typescript\nimport {\n ParentHandshake, WorkerMessenger, RemoteHandle\n} from 'post-me';\n\nimport { WorkerMethods, WorkerEvents } from './types';\n\nconst worker = new Worker('./worker.js');\n\nconst messenger = new WorkerMessenger({ worker });\n\nParentHandshake(messenger).then((connection) =\u003e {\n  const remoteHandle: RemoteHandle\u003cWorkerMethods, WorkerEvents\u003e\n    = connection.remoteHandle();\n\n  // Call methods on the worker and get the result as a Promise\n  remoteHandle.call('sum', 3, 4).then((result) =\u003e {\n    console.log(result); // 7\n  });\n\n  // Listen for a specific custom event from the app\n  remoteHandle.addEventListener('ping', (payload) =\u003e {\n    console.log(payload) // 'Oh, hi!'\n  });\n\n  // The following lines have various mistakes that will be caught by the compiler\n  remoteHandle.call('mul', 3, 'four'); // Wrong argument type\n  remoteHandle.call('foo'); // 'foo' doesn't exist on WorkerMethods type\n});\n```\n\nWorker code:\n```typescript\nimport { ChildHandshake, WorkerMessenger, LocalHandle } from 'post-me';\n\nimport { WorkerMethods, WorkerEvents } from './types';\n\nconst methods: WorkerMethods = {\n  sum: (x: number, y: number) =\u003e x + y,\n  mul: (x: number, y: number) =\u003e x * y,\n}\n\nconst messenger = WorkerMessenger({worker: self});\nChildHandshake(messenger, methods).then((connection) =\u003e {\n  const localHandle: LocalHandle\u003cWorkerMethods, WorkerEvents\u003e\n    = connection.localHandle();\n\n  // Emit custom events to the worker\n  localHandle.emit('ping',  'Oh, hi!');\n});\n```\n\n\u003ca id=\"other\"\u003e\u003c/a\u003e\n\n## Other Uses\npost-me can establish the same level of bidirectional communications not only with workers but with other windows too (e.g. iframes) and message channels.\n\nInternally, the low level differences between communicating with a `Worker`, a `Window`, or a `MessageChannel` have been abstracted, and the `Handshake` will accept any object that implements the `Messenger` interface defined by __post-me__.\n\nThis approach makes it easy for post-me to be extended by its users.\n\nA `Messenger` implementation for communicating between `Windows` and `MessagePorts` is already provided in the library (`WindowMessenger` and `PortMessenger`).\n\n\u003ca id=\"windows\"\u003e\u003c/a\u003e\n\n### Windows\nHere is an example of using post-me to communicate with an iframe.\n\nParent code:\n```typescript\nimport { ParentHandshake, WindowMessenger } from 'post-me';\n\n// Create the child window any way you like (iframe here, but could be popup or tab too)\nconst childFrame = document.createElement('iframe');\nconst childWindow = childFrame.contentWindow;\n\n// For safety it is strongly adviced to pass the explicit child origin instead of '*'\nconst messenger = new WindowMessenger({\n  localWindow: window,\n  remoteWindow: childWindow,\n  remoteOrigin: '*'\n});\n\nParentHandshake(messenger).then((connection) =\u003e {/* ... */});\n```\n\nChild code:\n```typescript\nimport { ChildHandshake, WindowMessenger } from 'post-me';\n\n// For safety it is strongly adviced to pass the explicit child origin instead of '*'\nconst messenger = new WindowMessenger({\n  localWindow: window,\n  remoteWindow: window.parent,\n  remoteOrigin: '*'\n});\n\nChildHandshake(messenger).then((connection) =\u003e {/* ... */});\n```\n\n\u003ca id=\"channels\"\u003e\u003c/a\u003e\n\n### MessageChannels\nHere is an example of using post-me to communicate over a `MessageChannel`.\n\n```typescript\nimport { ParentHandshake, ChildHandshake, PortMessenger } from 'post-me';\n\n// Create a MessageChannel\nconst channel = new MessageChannel();\nconst port1 = channel.port1;\nconst port2 = channel.port2;\n\n// In the real world  port1 and port2 would be transferred to other workers/windows\n{\n  const messenger = new PortMessenger({port: port1});\n  ParentHandshake(messenger).then(connection =\u003e {/* ... */});\n}\n{\n  const messenger = new PortMessenger({port: port2});\n  ChildHandshake(messenger).then(connection =\u003e {/* ... */});\n}\n```\n\n\u003ca id=\"callbacks\"\u003e\u003c/a\u003e\n\n## Callbacks as call parameters\nEven though functions cannot actually be shared across contexts, with a little magic under the hood __post-me__ let's you pass callback functions as arguments when calling a method on the other worker/window.\n\nPassing callbacks can be useful to obtain progress or partial results from a long running task.\n\nParent code:\n```typescript\n//...\nParentHandshake(messenger).then(connection =\u003e {\n  const remoteHandle = connection.remoteHandle();\n\n  const onProgress = (progress) =\u003e {\n    console.log(progress); // 0.25, 0.5, 0.75\n  }\n\n  remoteHandle.call(\"slowSum\", 2, 3, onProgress).then(result =\u003e {\n    console.log(result); // 5\n  });\n});\n```\n\nWorker code:\n```typescript\nconst methods = {\n  slowSum: (x, y, onProgress) =\u003e {\n    onProgress(0.25);\n    onProgress(0.5);\n    onProgress(0.75);\n\n    return x + y;\n}\n// ...\nChildHandshake(messenger, methods).then(connection =\u003e {/* */})\n```\n\n\u003ca id=\"transfer\"\u003e\u003c/a\u003e\n\n## Transfer vs Clone\nBy default any call parameter, return value, and event payload is cloned when passed to the other context.\n\nWhile in most cases this doesn't have a significant impact on performance, sometimes you might need to transfer an object instead of cloning it. NOTE: only `Transferable` objects can be transfered (`ArrayBuffer`, `MessagePort`, `ImageBitmap`, `OffscreenCanvas`).\n\n__post-me__ provides a way to optionally transfer objects that are part of a method call, return value, or event payload.\n\nIn the example below, the parent passes a very large array to a worker, the worker modifies the array in place, and returns it to the parent. Transfering the array instead of cloning it twice can save significant amounts of time.\n\nParent code:\n```typescript\n// ...\n\nParentHandshake(messenger).then((connection) =\u003e {\n  const remoteHandle = connection.remoteHandle();\n\n  // Transfer the buffer of the array parameter of every call that will be made to 'fillArray'\n  remoteHandle.setCallTransfer('fillArray', (array, value) =\u003e [array.buffer]);\n  {\n    const array = new Float64Array(100000000);\n    remoteHandle.call('fillArray', array, 5);\n  }\n\n  // Transfer the buffer of the array parameter only for this one call made to 'scaleArray'\n  {\n    const array = new Float64Array(100000000);\n    const args = [array, 2];\n    const callOptions = { transfer: [array.buffer] };\n    remoteHandle.customCall('scaleArray', args, callOptions);\n  }\n});\n```\n\nWorker code:\n```typescript\n// ...\n\nconst methods = {\n  fillArray: (array, value) =\u003e {\n    array.forEach((_, i) =\u003e {array[i] = value});\n    return array;\n  },\n  scaleArray: (buffer, type value) =\u003e {\n    array.forEach((a, i) =\u003e {array[i] = a * value});\n    return array;\n  }\n}\n\nChildHandshake(messenger, model).then((connection) =\u003e {\n  const localHandle = connection.localHandle();\n\n  // For each method, declare which parts of the return value should be transferred instead of cloned.\n  localHandle.setReturnTransfer('fillArray', (result) =\u003e [result.buffer]);\n  localHandle.setReturnTransfer('scaleArray', (result) =\u003e [result.buffer]);\n});\n```\n\n\u003ca id=\"debugging\"\u003e\u003c/a\u003e\n\n## Debugging\nYou can optionally output the internal low-level messages exchanged between the two ends.\n\nTo enable debugging, simply decorate any `Messenger` instance with the provided `DebugMessenger` decorator.\n\nYou can optionally pass to the decorator your own logging function (a glorified `console.log` by default), which can be useful to make the output more readable, or to inspect messages in automated tests.\n\n```typescript\nimport { ParentHandshake, WindowMessenger, DebugMessenger } from 'post-me';\n\nimport debug from 'debug';          // Use the full feature logger from the debug library\n// import { debug } from 'post-me'; // Or the lightweight implementation provided\n\nlet messenger = new WindowMessenger({\n  localWindow: window,\n  remoteWindow: childWindow,\n  remoteOrigin: '*'\n});\n\n// To enable debugging of each message exchange, decorate the messenger with DebugMessenger\nconst log = debug('post-me:parent'); // optional\nmessenger = DebugMessenger(messenger, log);\n\nParentHandshake(messenger).then((connection) =\u003e {\n  // ...\n});\n```\n\nOutput:\n![debug output](debug.png)\n\n\u003ca id=\"parallel\"\u003e\u003c/a\u003e\n\n## Parallel Programming\n[__@post-me/mpi__](https://github.com/alesgenova/post-me/tree/main/packages/mpi) is an experimental library to write parallel algorithms that run on a pool of workers using a MPI-like syntax. See the dedicated [README](https://github.com/alesgenova/post-me/tree/main/packages/mpi) for more information.\n\n\u003ca id=\"api\"\u003e\u003c/a\u003e\n\n## API Documentation\n\nThe full [__API reference__](https://alesgenova.github.io/post-me/post-me.html) can be found [here](https://alesgenova.github.io/post-me/post-me.html).\n\n\u003ca id=\"references\"\u003e\u003c/a\u003e\n\n## References\nThe __post-me__ API is loosely inspired by [postmate](https://github.com/dollarshaveclub/postmate), with several major improvements and fixes to outstanding issues:\n- Native typescript support\n- Method calls can have both arguments and a return value: ([#94](https://github.com/dollarshaveclub/postmate/issues/94))\n- Parent and child can both expose methods and/or events (instead of child only): [#118](https://github.com/dollarshaveclub/postmate/issues/118)\n- Exceptions that occur in a method call can be caught by the caller.\n- Better control over handshake origin and attempts: ([#150](https://github.com/dollarshaveclub/postmate/issues/150), [#195](https://github.com/dollarshaveclub/postmate/issues/195))\n- Multiple listeners for each event: ([#58](https://github.com/dollarshaveclub/postmate/issues/58))\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falesgenova%2Fpost-me","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falesgenova%2Fpost-me","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falesgenova%2Fpost-me/lists"}