npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

quickbus

v1.0.1

Published

Promise-based RPC wrapper for postMessage transports.

Readme

quickbus

A small promise-based RPC layer for postMessage transports.

npm version npm license CI

quickbus lets you expose a handler object on one side of a messaging boundary and call it from the other side as if it were a local async API. It is intended for:

  • parent page <-> iframe communication
  • window <-> popup communication
  • page <-> service worker communication
  • MessagePort / MessageChannel communication

Installation

npm install quickbus

Importing

import { Client, Server } from 'quickbus';
const { Client, Server } = require('quickbus');

Quick Start

Parent Page To Iframe

Page:

import { Client } from 'quickbus';

const iframe = document.querySelector('iframe');
const frameOrigin = 'https://child.example.com';
const bus = Client.forIframe(iframe, frameOrigin);

const greeting = await bus.sayHello('World');
console.log(greeting);

Each client call returns an awaitable request handle. That means this still works:

const greeting = await bus.sayHello('World');

But you can also keep the handle and abort it locally if the caller decides to stop waiting:

const request = bus.sayHello('World');

setTimeout(() => request.abort(), 5000);

const greeting = await request;

Iframe:

import { Server } from 'quickbus';

const server = new Server({
  sayHello(to) {
    return `Hello, ${to}!`;
  }
}, 'https://parent.example.com');

window.addEventListener('message', event => {
  server.handleMessageEvent(event);
});

Child Iframe To Parent Window

Parent:

import { Server } from 'quickbus';

const server = new Server({
  sayHello(name) {
    return `Hello from ${name}!`;
  }
}, window.location.origin);

window.addEventListener('message', event => {
  server.handleMessageEvent(event);
});

Child iframe:

import { Client } from 'quickbus';

const bus = Client.forWindow(window.parent, window.location.origin);
const message = await bus.sayHello('Parent');
console.log(message);

Page To Service Worker

Page:

import { Client } from 'quickbus';

await navigator.serviceWorker.register('/sw.mjs', { type: 'module' });
await navigator.serviceWorker.ready;

const bus = Client.forServiceWorker(navigator.serviceWorker);
const greeting = await bus.sayHello('Worker');
console.log(greeting);

Service worker:

import { Server } from 'quickbus';

self.addEventListener('install', () => self.skipWaiting());
self.addEventListener('activate', event => event.waitUntil(self.clients.claim()));

const server = new Server({
  sayHello(to) {
    return `Hello, ${to}!`;
  }
});

self.addEventListener('message', event => {
  server.handleMessageEvent(event);
});

Service Worker To Page

Page:

import { Server } from 'quickbus';

const server = new Server({
  getOpenTabs() {
    return Array.from(document.querySelectorAll('[data-tab]'))
      .map(tab => tab.getAttribute('data-tab'));
  }
});

window.addEventListener('message', event => {
  server.handleMessageEvent(event);
});

Service worker:

import { Client } from 'quickbus';

self.addEventListener('message', async event => {
  if(event.data?.action !== 'inspect-tabs')
  {
    return;
  }

  const client = Client.forWindow(event.source);
  const tabs = await client.getOpenTabs();

  console.log(tabs);
});

MessagePort

import { Client, Server } from 'quickbus';

const channel = new MessageChannel();

const client = Client.forMessagePort(channel.port1);
const server = new Server({
  add(a, b) {
    return a + b;
  }
});

channel.port2.addEventListener('message', event => {
  server.handleMessageEvent(event);
});

channel.port2.start?.();
channel.port1.start?.();

console.log(await client.add(2, 3));

Client API

new Client({ to, from?, origin? })

Creates a proxy-backed RPC client.

Parameters:

  • to: required postMessage target
  • from: optional event target that receives reply message events
  • origin: optional targetOrigin for outbound postMessage(...)

Example:

const bus = new Client({
  to: iframe.contentWindow,
  from: window,
  origin: 'https://child.example.com'
});

Notes:

  • If from is omitted, Client first tries globalThis.
  • If globalThis cannot receive message events in the current runtime, it falls back to to when possible.
  • The constructor accepts a named options object only.
  • Each RPC method returns a promise-like request handle with .abort().
  • Aborting a request clears the local pending token, but does not send a cancellation message to the remote transport.

Client.forIframe(iframe, origin?, from?)

Convenience wrapper for parent-page-to-iframe messaging.

Equivalent to:

new Client({
  to: iframe.contentWindow,
  from,
  origin
});

When from is omitted, the normal Client fallback logic applies, which usually resolves to the parent window.

Client.forWindow(targetWindow, origin?, from?)

Convenience wrapper for window or popup targets such as:

  • window.parent
  • window.opener
  • a handle returned by window.open(...)

Client.forServiceWorker(serviceWorkerOrContainer, from?)

Convenience wrapper for service worker messaging from a page.

Accepted inputs:

  • navigator.serviceWorker
  • navigator.serviceWorker.controller
  • a ServiceWorker-like target

If you pass a ServiceWorkerContainer such as navigator.serviceWorker, quickbus uses:

  • to = navigator.serviceWorker.controller
  • from = navigator.serviceWorker by default

That default matters because service worker replies are delivered on the container, not the page window.

Client.forMessagePort(port, origin?)

Convenience wrapper for MessagePort.

This uses the same port for both directions:

  • to = port
  • from = port

Server API

new Server(handler, ...origins)

Creates an RPC server that dispatches inbound actions to handler.

Parameters:

  • handler: object whose function properties implement your RPC methods
  • ...origins: optional allowlist of acceptable event.origin values

Example:

const server = new Server(handler, 'https://client.example.com');

Behavior:

  • If no origins are provided, replies are allowed by default.
  • If one or more origins are provided, replies are only sent when event.origin matches one of them.
  • Responses are posted back through event.source.

server.handleMessageEvent(event)

Handles one inbound message event.

Typical usage:

window.addEventListener('message', event => {
  server.handleMessageEvent(event);
});

The handler return value may be synchronous or async. Errors are caught, serialized with JSON.stringify, and returned as the error field in the reply payload.

Protocol

Outgoing request shape:

{
  action: 'methodName',
  params: [arg1, arg2],
  token: 'uuid'
}

Reply shape:

{
  re: 'uuid',
  result: value,
  error: serializedError
}

Security Notes

  • Always pass explicit origins for cross-origin iframe or window messaging.
  • Server origin filtering is opt-in. If you want origin enforcement, provide one or more origins to the constructor.
  • Client does not currently validate reply origin before resolving a pending token. If you need stronger guarantees, pair it with explicit server origin controls and a trusted channel topology.

Mental Model

quickbus has two pieces:

  • Server listens for inbound message events, dispatches the requested action to a handler object, and posts the result back to the sender.
  • Client sends { action, params, token } messages and resolves a promise when the matching { re: token, result, error } reply arrives.

The important design detail is that the place you send to is not always the place you listen on:

  • parent page -> iframe: to = iframe.contentWindow, from = window
  • child iframe -> parent: to = window.parent, from = window
  • MessagePort: to = port, from = port
  • service worker from a page: to = navigator.serviceWorker.controller, from = navigator.serviceWorker

That is why Client uses named transport options and wrapper helpers instead of a single positional constructor.

Development

Available scripts:

npm run build
npm test
npm run test:e2e
npm run lint
npm run tsc

What they do:

  • npm run build: rebuilds the published root artifacts from source/
  • npm test: runs the Node unit tests in test/*.test.mjs
  • npm run test:e2e: runs the Playwright browser transport tests
  • npm run lint: runs ESLint on source/
  • npm run tsc: runs TypeScript checking against the JSDoc-typed source

Current Browser Coverage

The Playwright suite currently verifies:

  • parent page -> iframe RPC
  • child iframe -> parent window RPC
  • page -> service worker RPC

License

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Copyright 2025-2026 Sean Morris