@lopatnov/browser-tab-ipc
v2.1.0
Published
Inter-process communication mechanism between browser tabs. A bus network between browser tabs.
Maintainers
Readme
browser-tab-ipc
Lightweight cross-tab messaging library for TypeScript / JavaScript. Exchange messages between browser tabs with automatic transport selection and graceful fallback.
Browser Support
| Transport | Chrome | Firefox | Safari | Edge | Notes | | ---------------- | ------ | ------- | ------ | ---- | ------------------------------ | | BroadcastChannel | 54+ | 38+ | 15.4+ | 79+ | Default — fastest, same-origin | | SharedWorker | 4+ | 29+ | 16+ | 79+ | Cross-origin capable | | SessionStorage | all | all | all | all | Universal fallback |
Table of Contents
- Installation
- Quick Start
- How It Works
- API Reference
- SharedWorker Setup
- Troubleshooting
- Contributing
- License
Installation
npm:
npm install @lopatnov/browser-tab-ipcyarn:
yarn add @lopatnov/browser-tab-ipcCDN (UMD, no bundler required):
<script src="https://lopatnov.github.io/browser-tab-ipc/dist/library.umd.min.js"></script>Quick Start
import {BrowserTabIPC} from '@lopatnov/browser-tab-ipc';
const ipc = new BrowserTabIPC();
// Listen for messages from other tabs
ipc.message((data) => {
console.log('Received:', data);
});
// Connect and send
await ipc.connect();
await ipc.postMessage({event: 'tab-opened', tabId: crypto.randomUUID()});Open the same page in multiple tabs — every tab receives the message instantly.
How It Works
BrowserTabIPC tries each transport in order and uses the first one that connects successfully:
BroadcastChannel → SharedWorker → SessionStorage
(fastest) (flexible) (always works)You can override this by specifying a single transport or a custom fallback list via ConnectionOptions.
API Reference
class BrowserTabIPC
BrowserTabIPC extends Node's EventEmitter and provides a clean connect / send / receive interface.
new BrowserTabIPC(options?: ConnectionOptions)
Creates an instance. Options set here apply to all subsequent connect() calls.
import {BrowserTabIPC, TransportType} from '@lopatnov/browser-tab-ipc';
// Default — auto-selects from all three transports
const ipc = new BrowserTabIPC();
// Force a single transport
const ipc = new BrowserTabIPC({
transportTypes: TransportType.broadcastChannel,
});
// Custom fallback chain
const ipc = new BrowserTabIPC({
transportTypes: [TransportType.sharedWorker, TransportType.sessionStorage],
sharedWorkerUri: '/ipc-worker.js',
});connect(options?: ConnectionOptions): Promise<ConnectionState>
Establishes the connection. Options passed here are merged with constructor options.
const state = await ipc.connect({
sharedWorkerUri: '/dist/ipc-worker.js',
storageKey: 'my-app-channel',
storageExpiredTime: 30_000,
});
console.log(state.connected); // true
console.log(state.type); // e.g. TransportType.broadcastChanneldisconnect(): Promise<ConnectionState>
Closes the active connection and cleans up all listeners and timers.
const state = await ipc.disconnect();
console.log(state.connected); // falsepostMessage(message: any): Promise<void>
Broadcasts a serializable value to all connected tabs.
await ipc.postMessage('ping');
await ipc.postMessage({type: 'STORE_UPDATE', payload: {count: 42}});Event subscription methods
| Method | Trigger |
| --------------------------- | --------------------------------------- |
| message(callback) | A message was received from another tab |
| connected(callback) | Connection established successfully |
| connectionError(callback) | Connection attempt failed |
| disconnected(callback) | Connection was closed |
ipc.message((data) => console.log('Message:', data));
ipc.connected((state) => console.log('Connected via', TransportType[state.type!]));
ipc.connectionError((state) => console.error('Connection failed:', state.error));
ipc.disconnected(() => console.log('Disconnected'));You can also use the EventEmitter API directly with the exported event name constants:
import {EventMessage, EventConnected, EventConnectionError, EventDisconnected} from '@lopatnov/browser-tab-ipc';
ipc.on(EventMessage, (data) => {
/* ... */
});
ipc.once(EventConnected, (state) => {
/* ... */
});ConnectionOptions
| Option | Type | Default | Description |
| -------------------- | ---------------------------------- | ----------------------- | ----------------------------------------------- |
| transportTypes | TransportType \| TransportType[] | All three, in order | Transport(s) to try, left to right |
| sharedWorkerUri | string | GitHub CDN fallback URL | URL to ipc-worker.js (SharedWorker transport) |
| storageKey | string | 'ipc' | Namespace prefix for SessionStorage keys |
| storageExpiredTime | number | 30000 | Message TTL in milliseconds (SessionStorage) |
ConnectionState
Returned by connect() and disconnect(), and passed to event callbacks.
| Field | Type | Description |
| ----------- | ----------------------- | --------------------------------------------- |
| type | TransportType \| null | Active transport, or null if none connected |
| connected | boolean | Whether the connection is currently active |
| error? | unknown | Error detail when a connection attempt fails |
TransportType
import {TransportType} from '@lopatnov/browser-tab-ipc';
TransportType.broadcastChannel; // BroadcastChannel API
TransportType.sharedWorker; // SharedWorker
TransportType.sessionStorage; // SessionStorage eventsEvents
| Constant | When emitted |
| ---------------------- | ---------------------------------- |
| EventConnected | A transport connected successfully |
| EventConnectionError | A transport failed to connect |
| EventDisconnected | The connection was closed |
| EventMessage | A message arrived from another tab |
SharedWorker Setup
The SharedWorker transport requires a worker script served from your own origin to avoid CORS issues. Copy the bundled file into your project:
cp node_modules/@lopatnov/browser-tab-ipc/dist/ipc-worker.js public/Then point connect() to it:
await ipc.connect({sharedWorkerUri: '/ipc-worker.js'});Without this step, the library falls back to a GitHub-hosted worker — which only works on the same origin as the CDN.
Troubleshooting
Module '"events"' can only be default-imported using the 'allowSyntheticDefaultImports' flag
Add to tsconfig.json:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true
}
}Messages are not received in other tabs
- All tabs must be on the same origin (protocol + host + port).
- BroadcastChannel and SessionStorage are strictly same-origin.
- SharedWorker can bridge origins if the worker file is served from the target origin.
SharedWorker fails silently
- Open DevTools → Application → Shared Workers and check for errors.
- Verify the
sharedWorkerUripath is accessible from the browser (check for 404). - If the file is missing, copy
ipc-worker.jsto yourpublic/folder as shown above.
Connection established but no messages arrive
- Both tabs must call
connect()before any messages are sent. - A tab does not receive its own messages — only other tabs do.
Contributing
Contributions are welcome! Please read CONTRIBUTING.md before opening a pull request.
- Bug reports → open an issue
- Security vulnerabilities → GitHub Security Advisories (do not use public issues)
- Questions → Discussions
- Found it useful? A star on GitHub helps others discover the project
Built With
- TypeScript — strict typing throughout
- Rollup — bundled to ESM, CJS, and UMD formats
- BroadcastChannel API — primary transport
- SharedWorker API — cross-tab shared execution context
- Web Storage API — universal fallback transport
- Node.js EventEmitter — event-driven API
- Yarn — package management and script runner
- Babel — TypeScript transpilation pipeline for tests
- Puppeteer + Jest — cross-tab integration testing in a real browser
- ESLint + Prettier — linting and formatting
License
Apache-2.0 © 2019–2026 Oleksandr Lopatnov · LinkedIn
