@jam.dev/rimless
TypeScript icon, indicating that this package has built-in type declarations

0.6.3 • Public • Published

rimless

npm Commitizen friendly npm bundle size

Rimless makes event based communication easy with a promise-based API wrapping postMessage. Works with iframes, webworkers, and Node.js worker threads.

You can use rimless to call remote procedures, exchange data or expose local functions with iframes, webworkers, or Node.js workers.

You can see it in action in the code sandbox below:

CodeSandbox

Installation

Rimless can be installed via npm.

$ npm i -S @jam.dev/rimless

or from a CDN

<script src="https://unpkg.com/@jam.dev%2Frimless/lib/rimless.min.js"></script>

Example Usage

Below is a minimal but complete round‑trip that shows how each side can:

  1. expose variables and functions,
  2. read the other side’s variables,
  3. invoke the other side’s functions,
  4. finally close the link.

Host (page that embeds the iframe)

import { host } from "@jam.dev/rimless";

const iframe = document.getElementById("myIframe");

// Everything inside this object is exported to the guest
const hostApi = {
  someHostVariable: 12,
  someHostFunction: (value) => `hello ${value}`,
};

const connection = await host.connect(iframe, hostApi);

// ↘︎  Access data that the iframe exposed
console.log(connection.remote.someGuestVariable); // → 42

// ↘︎  Call a guest‑side RPC and await its result
const result = await connection.remote.someGuestFunction("here");
console.log(result); // → "hello here"

// Done talking? Tear down the channel.
connection.close();

Guest (code that runs inside the iframe)

import { guest } from "@jam.dev/rimless";

// The object you pass to guest.connect is your public surface
const guestApi = {
  someGuestVariable: 42,
  someGuestFunction: (value) => `hello ${value}`,
};

const connection = await guest.connect(guestApi);

// ↗︎  Read a host‑side value
console.log(connection.remote.someHostVariable); // → 12

// ↗︎  Invoke a host‑side RPC
const res = await connection.remote.someHostFunction("there");
console.log(res); // → "hello there"

// Close when finished to free resources
connection.close();

What to remember

  • connection.remote is the automatically generated proxy for the other side’s exports.
  • Every remote call returns a Promise, so feel free to await it.
  • Always call connection.close() when you no longer need the tunnel—this removes event listeners and avoids memory leaks.

Getting Started

This is how you can connect your website to an iframe or webworker:

import { host } from "@jam.dev/rimless";

const iframe = document.getElementById("myIframe");
const worker = new Worker("myWorker");

// connect to the iframe
host.connect(iframe);

// connect to the worker
host.connect(worker);

You also need to connect your iframe/webworker to the host website.

Usage from an iframe:

import { guest } from "@jam.dev/rimless";

// connect to the parent website
guest.connect();

Usage from a webworker:

importScripts("https://unpkg.com/@jam.dev%2Frimless/lib/rimless.min.js");

const { guest } = rimless;

// connect to the parent website
guest.connect();

Exposing an API

To 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.

import { host } from "@jam.dev/rimless";

const api = {
  setColor: (color) => {
    document.body.style.background = color;
  },
};

const iframe = document.getElementById("myIframe");

host.connect(iframe, api);

The api schema must be passed on connection, the same applies to the iframe/webworker.

Calling an RPC

With the host API exposed we can now invoke the remote procedure from the iframe.

import { guest } from "@jam.dev/rimless";

// connect returns a promise that resolves in a connection object
// `connection.remote` contains the api you can invoke
guest.connect().then((connection) => {
  connection.remote.setColor("#011627");
});

Calling the remote from an RPC

Every RPC handler you expose receives the caller’s method collection as its last parameter—conventionally named remote. That means an RPC can immediately call back into the opposite context to acknowledge success, return extra data, or kick‑off a follow‑up action.

Why it’s useful

  • Confirm completion – send a quick “done!” message when a long‑running task finishes.
  • Chain operations – perform a host‑side update, then ask the guest to re‑render.
  • Stream results – push incremental data to the caller instead of waiting for one big response.
// host (parent window)
import { host } from "rimless";

const api = {
  /**
   * Change the page background, then notify the guest.
   * @param {string} color  Hex or CSS color string
   * @param {object} remote Automatically injected guest‑side RPCs
   */
  setColor: (color, remote) => {
    document.body.style.background = color;
    remote.logMessage("Background updated ✔");
  },
};

const iframe = document.getElementById("myIframe");
host.connect(iframe, api);
// guest (inside the iframe)
import { guest } from "rimless";

const api = {
  /** Show messages from the host */
  logMessage: (msg) => console.log(msg),
};

const { remote } = await guest.connect(api);

// Ask the host to change its background.
// Afterwards, the guest will receive logMessage("Background updated ✔").
remote.setColor("#011627");

Key points

  • Handler signature – (…args, remote); you can ignore remote if you don’t need it.
  • Promises everywhere – RPC calls return promises, so you can await remote.someMethod().
  • Keep it short – avoid deep call‑chains that bounce endlessly between host and guest.

Closing a connection

Closing a connection will remove all event listeners that were registered.

import { guest } from "@jam.dev/rimless";

guest.connect().then((connection) => {
  connection.close();
});

How it Works

  1. The guest (iframe/webworker) sends a handshake request to the host with a schema describing its API
  2. The host confirms the handshake and returns a schema with its own API

Now both can make use of the APIs they have shared with each other, e.g.

  1. The guest requests someAction on the parent.
  2. After verifying the origin, the parent will execute the function mapped to someAction and the result is returned to the guest.

Limitations

All parameters passed through postMessage need to be serializable. This applies also for all return values of the functions you expose.

// someFunction would return undefined when called in the remote.
const api = {
  someFunction: () => () => {},
};

Alternatives

This library is inspired by Postmate and Penpal.

So why does this library exist?

  • works with webworkers!
  • works with Node.js worker threads
  • does not create the iframe (easier to work with libraries like react)
  • works with iframes using srcdoc
  • works with multiple iframes from the same origin
  • remote RPC handlers receive the caller's API as the last argument

API

Rimless exports two objects: host and guest.

host.connect

Connect your website to a "guest" (iframe/webworker).

host.connect(iframe, {
  log: (value) => console.log(value),
});
Name Type Description Required
guest HTMLIFrameElement or Worker Target of the connection required
schema object schema of the api you want to expose -

guest.connect

Connect a "guest" to your website. The guest connection automatically happens based on the environment it is run.

guest.connect({
  log: (value) => console.log(value),
});
Name Type Description Default
schema object schema of the api you want to expose -
eventHandlers object lifecycle callbacks like onConnectionSetup -

onConnectionSetup(remote) is called once the handshake completes. It receives the remote API so you can perform setup logic before using the connection.


License

MIT

Readme

Keywords

none

Package Sidebar

Install

npm i @jam.dev/rimless

Weekly Downloads

720

Version

0.6.3

License

MIT

Unpacked Size

98.2 kB

Total Files

13

Last publish

Collaborators

  • prestonjam
  • irtefajam
  • tonydotjam
  • dani0000
  • cyrusatjam
  • ruiatjam