data-transport orchestrates request-response messaging across iframes, workers, browser extensions, Node.js processes, Electron, BroadcastChannel, and WebRTC peers with one consistent API. Each transport handles connection setup, timeouts, and logging so you can focus on your payloads.
- data-transport unlocks cross-context messaging
- Why data-transport reduces boilerplate
- Install the package
- Start with a request-response example
- Understand how transports are organized
- Combine advanced capabilities when you need them
- Run the examples locally
- Develop and contribute with confidence
The library exposes a small set of composable primitives: a base Transport class, the createTransport factory, decorators for registering listeners, and helpers for merging or mocking transports. All transports enforce the same request-response contract, share timeout handling, and use unique identifiers under the hood to avoid collisions.
- One API everywhere. Swap the transport key to reuse the same emit and listen code across iframes, workers, extensions, WebRTC, BroadcastChannel, or Node.js child processes.
- Bi-directional by default. Every emit returns a promise, and listeners can opt out of responding for fire-and-forget events.
- Connection-aware transports. Iframe, worker, and browser-extension transports delay sends until the peer reports that it is ready, exposing
onConnectandonDisconnecthooks when the runtime supports them. - Structured logging and serialization. Pass
serializer,timeout,prefix, andloggeroptions once to standardize payload formatting and diagnostics. - Testing-friendly helpers.
mockPorts()provides in-memory listeners for unit tests, whilemerge()fans out messages to multiple transports without re-registering listeners.
Install from npm or yarn and let TypeScript discover the included type definitions.
npm install data-transport
# or
yarn add data-transport
# or
pnpm add data-transportDefine interaction types:
type Internal = {
hello(options: { num: number }, word: string): Promise<{ text: string }>;
};Create transport in main page:
import { createTransport } from 'data-transport';
const external = createTransport<'IFrameMain', { listen: Internal }>('IFrameMain');
external.listen('hello', async (num) => ({ text: `hello ${num}` }));Create transport in the iframe:
import { createTransport } from 'data-transport'
const internal = createTransport<'IFrameInternal', { emit: Internal }>('IFrameInternal');
expect(await internal.emit('hello', { num: 42 }, 'Universe')).toEqual({ text: 'hello 42 Universe' });createTransport(name, options) instantiates the matching transport class. The table lists the available keys and highlights when to use them.
| Transport key | Runtime | Highlights |
|---|---|---|
MessageTransport |
Any window | Uses window.postMessage for simple page-to-page messaging. |
IFrameMain |
Host window | Targets a specific iframe, includes handshake and reload handling. |
IFrameInternal |
Iframe window | Connects back to the parent and syncs on reload. |
Broadcast |
Modern browsers | Wraps BroadcastChannel, configurable channel name or instance. |
WebWorkerClient |
Main thread | Sends transferable objects to a Worker, exposes onConnect. |
WebWorkerInternal |
Worker thread | Mirrors the client transport and queues emits until connected. |
SharedWorkerClient |
Page connected to a SharedWorker |
Auto-sends connect and disconnect signals, exposes onConnect. |
SharedWorkerInternal |
Shared worker | Tracks ports, broadcasts to all clients, and surfaces onConnect/onDisconnect. |
ServiceWorkerClient |
Page controlled by a service worker | Handles Safari serialization quirks via the useOnSafari flag. |
ServiceWorkerService |
Service worker | Routes responses back to the correct client, supporting _clientId. |
BrowserExtensions |
Generic extension context | Bridges browser.runtime.sendMessage to transports. |
BrowserExtensionsMain |
Background/service worker script | Manages ports and emits connect/disconnect callbacks. |
BrowserExtensionsClient |
Content script or popup | Connects over runtime.connect, supports onConnect. |
ElectronMain |
Electron main process | Uses IPC to communicate with renderer windows. |
ElectronRenderer |
Electron renderer process | Talks back to the main process over the same channel. |
WebRTC |
WebRTC data channel | Chunks large payloads, queues writes when buffers fill. |
MainProcess |
Node.js parent process | Wraps child.send/child.on. |
ChildProcess |
Node.js child process | Wraps process.send/process.on. |
Each transport accepts the generic TransportOptions so you can override listener, sender, timeout, serializer, or logger to match your environment.
| Option | Required | Default | Purpose |
|---|---|---|---|
listener: (callback) => (() => void) | void |
Yes | — | Attach a low-level event handler to the underlying channel. Return a disposer to avoid warnings from the constructor’s safety checks. |
sender: (message) => void |
Yes | — | Deliver outbound messages. Remove the transfer array before forwarding if the runtime demands it. |
timeout: number |
No | 60000 (ms) |
Max wait before an emit rejects with a timeout warning when a response is expected. |
verbose: boolean |
No | false |
Switch on structured logging for every send/receive. Use logger to pipe it elsewhere; otherwise console.info is used. |
prefix: string |
No | DataTransport |
Namespace for action names. Helpful when multiple transports share the same channel. |
listenKeys: string[] |
No | [] |
Class method names that should be auto-registered as listeners. In dev builds, calling them directly throws to prevent misuse. |
checkListen: boolean |
No | true |
Keep dev-time guards that surface duplicate responses or missing listener decorators. Toggle off to silence those warnings in production. |
serializer: { stringify?: (data) => string; parse?: (text) => any } |
No | — | Supply custom codecs for runtimes with serialization constraints (e.g., structured cloning gaps). Both functions are optional, so you can enable only one direction. |
logger: (options) => void |
No | — | Replace the default verbose logger. Receives the raw request/response payload for auditing. |
Every custom transport you construct via createTransport simply forwards these options to the base Transport class, so you can rely on them in any environment (browser, worker, Node.js, or extensions).
Use the provided @listen decorator to attach class methods as listeners without exposing them for manual calls.
import { Transport, listen, mockPorts } from 'data-transport';
const ports = mockPorts();
class ExternalTransport extends Transport {
constructor() {
super(ports.create());
}
@listen
async ping() {
return 'pong';
}
}emit accepts either the event name or an options object. Set respond: false for fire-and-forget events, change timeout, pass silent to suppress timeout warnings, and use _extra to forward metadata without polluting your payload.
await transport.emit(
{ name: 'notify', respond: false, _extra: { source: 'dashboard' } },
{ status: 'ready' }
);merge(first, second, ...others) combines transports so all listeners receive the same events while respecting the shared timeout, serializer, and logger.
import { createTransport, merge } from 'data-transport';
const broadcast = createTransport('Broadcast', {});
const serviceWorker = createTransport('ServiceWorkerClient', { worker });
const merged = merge(broadcast, serviceWorker);
await merged.emit('announce', { version: '5.0.3' });mockPorts() provides in-memory listener/sender pairs so you can assert end-to-end flows in Jest or any node-based test runner.
const ports = mockPorts();
const internal = createTransport('Base', ports.main);
const external = createTransport('Base', ports.create());Iframes, workers, browser extensions, and shared workers expose .onConnect() (and .onDisconnect() where supported) so you can delay expensive initialization until a peer is actually present. WebRTC transports buffer messages when the data channel is saturated and replay them once the browser signals that the buffer dropped below bufferedAmountLow.
Real-world samples live in the examples directory, covering BroadcastChannel, browser extensions, Electron, iframes, Node.js, service workers, shared workers, WebRTC, and web workers.
- Clone the repository.
- Install dependencies with
yarn. - Run the example you care about by opening the matching folder (for example,
examples/webworker) and following the instructions documented inside. - Try the hosted BroadcastChannel demo on CodeSandbox: data-transport Broadcast example.
yarn buildcompiles TypeScript and bundles the distributable with Rollup.yarn testexecutes the Jest suite, including transport handshakes and serializer scenarios.yarn cleanremoves build artifacts, whileyarn prettierenforces formatting insrc.- The project ships type definitions (
dist/index.d.ts) so downstream TypeScript projects get autocomplete out of the box.