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

@arnoldgraf/cross-tab-worker

v0.1.7

Published

Drop-in coordination wrapper that keeps exactly one Worker alive across all same-origin tabs.

Readme

cross-tab-worker

A drop-in coordination wrapper that keeps exactly one Worker alive across all same-origin browser tabs. Useful for OPFS access from Workers when using multiple tabs.

Exactly one tab owns the real Worker at a time (the leader). All other tabs (followers) send messages through a direct MessagePort to the leader, which forwards them to the worker. When the leader tab closes, one follower is automatically elected as the new leader. The application sees the same postMessage / onmessage interface regardless of role.


Why not SharedWorker?

SharedWorker doesn't support FileSystemSyncAccessHandle — the synchronous OPFS API that high-performance SQLite/WASM VFS implementations require. This library achieves the same multi-tab sharing using a regular dedicated Worker owned by one tab, with coordination through Web Locks and a tiny broker SharedWorker.


Usage

import { CrossTabWorker } from '@arnoldgraf/cross-tab-worker';

const worker = new CrossTabWorker(
  'my-db-worker',                              // stable name — used as the lock key
  () => new Worker(new URL('./db.worker.ts', import.meta.url), { type: 'module' }),
);

worker.onmessage = (e) => console.log('from worker:', e.data);

// Zero-copy if this tab is the leader; relayed through a direct MessagePort if follower.
const buffer = new ArrayBuffer(4096);
worker.postMessage({ op: 'write', buf: buffer }, [buffer]);

API

new CrossTabWorker(name, factory)

| Parameter | Type | Description | | --- | --- | --- | | name | string | Unique identifier for this worker type. Used as the Web Lock key and SharedWorker name. Must be stable across page loads. | | factory | () => Worker | Called only in the leader tab to instantiate the real Worker. |

Instance

| Member | Description | | --- | --- | | postMessage(message, transfer?) | Send a message to the worker. Zero-copy in the leader; transferred via a direct MessagePort in followers. | | onmessage | Callback for messages from the worker. | | onmessageerror | Callback for deserialization errors. | | addEventListener(type, handler) | 'message' or 'messageerror'. | | removeEventListener(type, handler) | | | destroy() | Leader: terminates the underlying Worker and releases the lock. Follower: closes ports and cancels the queued lock request. Safe to call in either role. | | isLeader | boolean getter — true if this tab currently owns the Worker. |


Architecture

Follower tab (B)                               Leader tab (A)
┌─────────────────────────────┐                 ┌────────────────────────────┐
│ CrossTabWorker (follower)   │                 │ CrossTabWorker (leader)    │
│  app.postMessage(msg, xfer) │                 │  ┌──────────────────────┐  │
│      │                      │                 │  │ Dedicated Worker     │  │
│      ▼                      │ direct channel  │  │ (real worker owner)  │  │
│  MessagePort (port1) ───────┼────────────────►│  └──────────────────────┘  │
└─────────────────────────────┘  relay msg/xfer │            ▲               │
                                                │            │ worker events │
                                                └────────────┼───────────────┘
                                                             │
                                                             ▼
                           ┌─────────────────────────────────────────────────────┐
                           │ port-broker SharedWorker                            │
                           │ - tab registry                                      │
                           │ - current leader tracking                           │
                           │ - forwards handshake ports (port2)                  │
                           │ - fans out coordination messages                    │
                           │   (`leader-ready`, `worker-msg`, `worker-msg-error`)│
                           └─────────────────────────────────────────────────────┘

Data path (zero-copy)

When a follower calls postMessage(msg, [transfer]):

  1. The message and its transfer list travel through the follower's direct MessagePort to the leader.
  2. Transferable objects (ArrayBuffer, etc.) are transferred (ownership moves) — no copy.
  3. The leader forwards them to the Worker with worker.postMessage(msg, transfer) — no copy again.

Two ownership transfers, zero copies.

Directed zero-copy responses

For bulk response data (e.g. query results), the worker can reply zero-copy to the specific tab that sent the request using the reply port attached to every relayed message:

// Inside the worker
self.onmessage = (e) => {
  const replyPort = e.ports[0]; // present when the message came from a follower tab
  const result = new ArrayBuffer(1024 * 1024);
  // ... fill result ...

  if (replyPort) {
    // Zero-copy: result is transferred directly to the requesting tab.
    replyPort.postMessage({ payload: { result }, transfer: [result] }, [result]);
  } else {
    // Broadcast fallback for leader-local callers (no relay port).
    self.postMessage({ result });
  }
};

The library unwraps the { payload, transfer } envelope and delivers payload directly to the follower's onmessage — no structured clone, no broadcast to other tabs.

When a message originates from the leader tab itself, e.ports[0] is absent (the leader posts directly to the worker without a relay). The self.postMessage(result) fallback handles that case and broadcasts to all tabs via the broker fan-out path.

Port handshake

On startup (or after failover), each follower:

  1. Creates a MessageChannel, keeps port1 for sending.
  2. Sends port2 to the broker, which forwards it to the leader tab.
  3. The leader holds port2 and listens for relay messages on it.

All subsequent data flows directly through the MessageChannel — the broker is not involved after the handshake.

No BroadcastChannel, no heartbeat

Leader death detection is handled entirely by the Web Locks API — when a leader tab closes or calls destroy(), the browser automatically releases the lock and wakes up the next follower. No periodic heartbeat is needed.

All other coordination — leader-ready and worker-msg — travels through the broker SharedWorker, which fans messages out to all registered tab ports. No BroadcastChannel is used anywhere.

Late-joining tabs

A tab that opens after the leader is established receives a leader-info message from the broker immediately on registration, so it can connect to the leader right away.

Failover sequence

  1. Leader tab closes → browser releases the Web Lock automatically.
  2. Every follower is already blocking on navigator.locks.request — exactly one wakes up and wins.
  3. Winner calls factory(), broadcasts leader-ready, drains its outbound buffer.
  4. Other followers receive leader-ready and re-establish direct ports to the new leader.
  5. Messages in-flight when the old leader closed are lost. Applications that require exactly-once delivery must implement their own sequence numbers.

Requirements

| API | Required | | --- | --- | | Web Locks (navigator.locks) | ✅ | | SharedWorker | ✅ | | MessageChannel / MessagePort | ✅ |

Throws a clear error on construction if either Web Locks or SharedWorker is unavailable.


Response delivery modes

| Scenario | Path | Copy? | | --- | --- | --- | | Worker → leader tab | Direct onmessage | Zero-copy (no transfer needed) | | Worker → all tabs (broadcast) | Broker fan-out via worker-msg | Structured clone | | Worker → specific follower (directed reply) | e.ports[0] reply port | Zero-copy |

Use the directed reply pattern for bulk response payloads. Use self.postMessage(data) (broadcast) for notifications or results that every tab needs to receive.