{"id":25022996,"url":"https://github.com/au-re/rimless","last_synced_at":"2026-05-10T10:57:06.394Z","repository":{"id":48272233,"uuid":"179559752","full_name":"au-re/rimless","owner":"au-re","description":"Event base communication made easy with a promise-based API wrapping postMessage.","archived":false,"fork":false,"pushed_at":"2025-01-22T07:13:35.000Z","size":4848,"stargazers_count":5,"open_issues_count":1,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-13T08:01:51.725Z","etag":null,"topics":["iframes","javascript","postmessage-api","webworkers"],"latest_commit_sha":null,"homepage":"https://codesandbox.io/p/sandbox/3qrqfl","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/au-re.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-04-04T19:04:11.000Z","updated_at":"2025-02-11T02:49:02.000Z","dependencies_parsed_at":"2024-08-21T17:32:41.222Z","dependency_job_id":"91a930fa-7a87-4eec-96db-541f69f64029","html_url":"https://github.com/au-re/rimless","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/au-re%2Frimless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/au-re%2Frimless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/au-re%2Frimless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/au-re%2Frimless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/au-re","download_url":"https://codeload.github.com/au-re/rimless/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248681478,"owners_count":21144700,"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":["iframes","javascript","postmessage-api","webworkers"],"created_at":"2025-02-05T14:34:24.265Z","updated_at":"2026-05-10T10:57:06.384Z","avatar_url":"https://github.com/au-re.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[npm-url]: https://www.npmjs.com/package/rimless\n[npm-image]: https://badge.fury.io/js/rimless.svg\n[commitizen-url]: http://commitizen.github.io/cz-cli/\n[commitizen-image]: https://img.shields.io/badge/commitizen-friendly-brightgreen.svg\n[license-url]: https://github.com/au-re/rimless/LICENSE\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/au-re/rimless/master/assets/icon.png\"/\u003e\n\u003c/p\u003e\n\n# rimless\n\n[![npm][npm-image]][npm-url]\n[![Commitizen friendly][commitizen-image]][commitizen-url]\n![npm bundle size](https://img.shields.io/bundlephobia/minzip/rimless)\n\n\u003e Rimless makes event based communication easy with a promise-based API wrapping [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). Works with **iframes**, **webworkers**, and **Node.js worker threads**.\n\nYou can use `rimless` to call remote procedures, exchange data or expose local functions with **iframes**, **webworkers**, or **Node.js workers**.\n\nYou can see it in action in the code sandbox below:\n\n\u003ca href=\"https://codesandbox.io/p/sandbox/3qrqfl\"\u003e\n\n![CodeSandbox](https://img.shields.io/badge/Codesandbox-040404?style=for-the-badge\u0026logo=codesandbox\u0026logoColor=DBDBDB)\n\n\u003c/a\u003e\n\n## Installation\n\nRimless can be installed via [npm](https://www.npmjs.com/package/rimless).\n\n```\n$ npm i -S rimless\n```\n\nor from a CDN\n\n```html\n\u003cscript src=\"https://unpkg.com/rimless/lib/rimless.min.js\"\u003e\u003c/script\u003e\n```\n\n## Example Usage\n\nBelow is a minimal but complete round‑trip that shows how each side can:\n\n1. expose variables and functions,\n2. read the other side’s variables,\n3. invoke the other side’s functions,\n4. finally close the link.\n\n**Host (page that embeds the iframe)**\n\n```js\nimport { host } from \"rimless\";\n\nconst iframe = document.getElementById(\"myIframe\");\n\n// Everything inside this object is exported to the guest\nconst hostApi = {\n  someHostVariable: 12,\n  someHostFunction: (value) =\u003e `hello ${value}`,\n};\n\nconst connection = await host.connect(iframe, hostApi);\n\n// ↘︎  Access data that the iframe exposed\nconsole.log(connection.remote.someGuestVariable); // → 42\n\n// ↘︎  Call a guest‑side RPC and await its result\nconst result = await connection.remote.someGuestFunction(\"here\");\nconsole.log(result); // → \"hello here\"\n\n// Done talking? Tear down the channel.\nconnection.close();\n```\n\n**Guest (code that runs inside the iframe)**\n\n```js\nimport { guest } from \"rimless\";\n\n// The object you pass to guest.connect is your public surface\nconst guestApi = {\n  someGuestVariable: 42,\n  someGuestFunction: (value) =\u003e `hello ${value}`,\n};\n\nconst connection = await guest.connect(guestApi);\n\n// ↗︎  Read a host‑side value\nconsole.log(connection.remote.someHostVariable); // → 12\n\n// ↗︎  Invoke a host‑side RPC\nconst res = await connection.remote.someHostFunction(\"there\");\nconsole.log(res); // → \"hello there\"\n\n// Close when finished to free resources\nconnection.close();\n```\n\n**What to remember**\n\n- `connection.remote` is the automatically generated proxy for the other side’s exports.\n- Every remote call returns a Promise, so feel free to await it.\n- Always call `connection.close()` when you no longer need the tunnel—this removes event listeners and avoids memory leaks.\n\n---\n\n## Getting Started\n\nThis is how you can **connect your website** to an iframe or webworker:\n\n```js\nimport { host } from \"rimless\";\n\nconst iframe = document.getElementById(\"myIframe\");\nconst worker = new Worker(\"myWorker\");\n\n// connect to the iframe\nhost.connect(iframe);\n\n// connect to the worker\nhost.connect(worker);\n```\n\nYou also need to **connect your iframe/webworker** to the host website.\n\nUsage from an iframe:\n\n```js\nimport { guest } from \"rimless\";\n\n// connect to the parent website\nguest.connect();\n```\n\nUsage from a webworker:\n\n```js\nimportScripts(\"https://unpkg.com/rimless/lib/rimless.min.js\");\n\nconst { guest } = rimless;\n\n// connect to the parent website\nguest.connect();\n```\n\n### Exposing an API\n\nTo do anything meaningful with this connection you need to provide a schema that defines **the API** of the host/iframe/webworker. Any serializable values as well as functions are ok to use. In the example below the host website provides a function that will update its background color when invoked.\n\n```js\nimport { host } from \"rimless\";\n\nconst api = {\n  setColor: (color) =\u003e {\n    document.body.style.background = color;\n  },\n};\n\nconst iframe = document.getElementById(\"myIframe\");\n\nhost.connect(iframe, api);\n```\n\nThe api schema must be passed on connection, the same applies to the `iframe/webworker`.\n\n### Calling an RPC\n\nWith the host API exposed we can now invoke the remote procedure from the iframe.\n\n```js\nimport { guest } from \"rimless\";\n\n// connect returns a promise that resolves in a connection object\n// `connection.remote` contains the api you can invoke\nguest.connect().then((connection) =\u003e {\n  connection.remote.setColor(\"#011627\");\n});\n```\n\n### Calling the remote from an RPC\n\nEvery RPC handler you expose receives the caller’s method collection as its last parameter—conventionally named `remote`.\nThat means an RPC can immediately call back into the opposite context to acknowledge success, return extra data, or kick‑off a follow‑up action.\n\n**Why it’s useful**\n\n- Confirm completion – send a quick “done!” message when a long‑running task finishes.\n- Chain operations – perform a host‑side update, then ask the guest to re‑render.\n- Stream results – push incremental data to the caller instead of waiting for one big response.\n\n```js\n// host (parent window)\nimport { host } from \"rimless\";\n\nconst api = {\n  /**\n   * Change the page background, then notify the guest.\n   * @param {string} color  Hex or CSS color string\n   * @param {object} remote Automatically injected guest‑side RPCs\n   */\n  setColor: (color, remote) =\u003e {\n    document.body.style.background = color;\n    remote.logMessage(\"Background updated ✔\");\n  },\n};\n\nconst iframe = document.getElementById(\"myIframe\");\nhost.connect(iframe, api);\n```\n\n```js\n// guest (inside the iframe)\nimport { guest } from \"rimless\";\n\nconst api = {\n  /** Show messages from the host */\n  logMessage: (msg) =\u003e console.log(msg),\n};\n\nconst { remote } = await guest.connect(api);\n\n// Ask the host to change its background.\n// Afterwards, the guest will receive logMessage(\"Background updated ✔\").\nremote.setColor(\"#011627\");\n```\n\n**Key points**\n\n- Handler signature – (…args, remote); you can ignore remote if you don’t need it.\n- Promises everywhere – RPC calls return promises, so you can await remote.someMethod().\n- Keep it short – avoid deep call‑chains that bounce endlessly between host and guest.\n\n### Closing a connection\n\nClosing a connection will remove all event listeners that were registered.\n\n```js\nimport { guest } from \"rimless\";\n\nguest.connect().then((connection) =\u003e {\n  connection.close();\n});\n```\n\n---\n\n## How it Works\n\n1. The guest (iframe/webworker) sends a handshake request to the host with a schema describing its API\n2. The host confirms the handshake and returns a schema with its own API\n\nNow both can make use of the APIs they have shared with each other, e.g.\n\n3. The guest requests `someAction` on the parent.\n4. After verifying the origin, the parent will execute the function mapped to `someAction` and the result is returned to the guest.\n\n## Limitations\n\nAll parameters passed through `postMessage` need to be serializable. This applies also for all return values of the functions you expose.\n\n```js\n// someFunction would return undefined when called in the remote.\nconst api = {\n  someFunction: () =\u003e () =\u003e {},\n};\n```\n\n## Alternatives\n\nThis library is inspired by [Postmate](https://www.npmjs.com/package/postmate) and [Penpal](https://www.npmjs.com/package/penpal).\n\n### So why does this library exist?\n\n- works with webworkers!\n- works with Node.js worker threads\n- does not create the iframe (easier to work with libraries like react)\n- works with iframes using srcdoc\n- works with multiple iframes from the same origin\n- remote RPC handlers receive the caller's API as the last argument\n\n---\n\n## API\n\nRimless exports two objects: `host` and `guest`.\n\n\u003e ### `host.connect`\n\nConnect your website to a \"guest\" (iframe/webworker).\n\n```js\nhost.connect(iframe, {\n  log: (value) =\u003e console.log(value),\n});\n```\n\n| Name     | Type                            | Description                          | Required |\n| -------- | ------------------------------- | ------------------------------------ | -------- |\n| `guest`  | `HTMLIFrameElement` or `Worker` | Target of the connection             | required |\n| `schema` | `object`                        | schema of the api you want to expose | -        |\n\n\u003e ### `guest.connect`\n\nConnect a \"guest\" to your website. The guest connection automatically happens based on the environment it is run.\n\n```js\nguest.connect({\n  log: (value) =\u003e console.log(value),\n});\n```\n\n| Name            | Type     | Description                                  | Default |\n| --------------- | -------- | -------------------------------------------- | ------- |\n| `schema`        | `object` | schema of the api you want to expose         | -       |\n| `eventHandlers` | `object` | lifecycle callbacks like `onConnectionSetup` | -       |\n\n`onConnectionSetup(remote)` is called once the handshake completes. It receives the remote API so you can perform setup logic before using the connection.\n\n\u003e ### `withTransferable`\n\nWrap RPC payloads that contain [transferable objects](https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects) so ownership moves between contexts instead of cloning the data. Useful for `ArrayBuffer`, `MessagePort`, `ReadableStream`, and similar types.\n\n```js\nimport { withTransferable } from \"rimless\";\n\nconst buffer = await file.arrayBuffer();\n\nawait connection.remote.processFile(\n  withTransferable((transfer) =\u003e ({\n    name: file.name,\n    buffer: transfer(buffer), // moves the ArrayBuffer to the worker\n  })),\n);\n```\n\n`withTransferable` invokes your callback with a `transfer` helper. Call it for every object you want to mark as transferable; the helper returns the same value so you can build payloads inline. The metadata is attached to the wrapper object without mutating your data.\n\nWorkers can also return transferables back to the caller:\n\n```js\nimport { guest, withTransferable } from \"rimless\";\n\nconst api = {\n  async processFile({ name, buffer }) {\n    const size = buffer.byteLength;\n\n    return withTransferable((transfer) =\u003e ({\n      summary: `Processed ${name} (${size} bytes)`,\n      buffer: transfer(buffer), // hands ownership back to the host\n    }));\n  },\n};\n\nawait guest.connect(api);\n```\n\n---\n\n## License\n\n[MIT](license-url)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fau-re%2Frimless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fau-re%2Frimless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fau-re%2Frimless/lists"}