BroadcastChannel Transport
The BroadcastChannel transport enables cross-context communication within the browser using the native BroadcastChannel API. Perfect for coordinating between tabs, windows, iframes, and Web Workers.
Overview
BroadcastChannel transport leverages the browser's built-in BroadcastChannel API to enable seamless communication between different browsing contexts that share the same origin. This is ideal for building multi-tab applications, Web Worker coordination, and iframe communication without server infrastructure.
Installation
npm install @irpclib/broadcastBasic Usage
1. Declare Functions (Shared)
// rpc/data/index.ts
import { irpc } from '../lib/module.js';
export type ProcessDataFn = (data: string) => Promise<string>;
export const processData = irpc.declare<ProcessDataFn>('processData', () => '');2. Implement Handlers (Worker)
// rpc/data/constructor.ts
import { irpc } from '../lib/module.js';
import { processData } from './index.js';
irpc.construct(processData, async (data) => {
return `Processed: ${data}`;
});3. Setup Router (Worker)
// worker.ts
import { BroadcastRouter } from '@irpclib/broadcast/router';
import { irpc, transport } from './lib/module.js';
import './rpc/data/constructor.js';
const router = new BroadcastRouter(irpc, transport);4. Client Usage (Main Thread)
// main.ts
import { processData } from './rpc/data/index.js';
const result = await processData('Hello from main thread');
console.log(result); // 'Processed: Hello from main thread'Configuration
BroadcastTransportConfig
type BroadcastTransportConfig = {
// Channel configuration
channel: string;
// Timeouts
timeout?: number; // Request timeout (default: 30000ms)
// Batching
debounce?: number | boolean; // Batching delay (inherited from base)
// Retry
maxRetries?: number; // Maximum retry attempts (inherited from base)
retryMode?: 'linear' | 'exponential'; // Retry strategy (inherited from base)
retryDelay?: number; // Base retry delay (inherited from base)
};Best Practice: Use Package Href
Recommended: Use irpc.href as the channel name to ensure consistency across all contexts:
const irpc = createPackage({
name: 'my-api',
version: '1.0.0',
});
const transport = new BroadcastTransport({
channel: irpc.href, // 'my-api/1.0.0'
});Benefits:
- Automatic uniqueness per package
- Version-aware communication
- No manual channel name management
- Prevents accidental channel conflicts
- Consistent across main thread, workers, and tabs
Use Cases
1. Web Worker Communication
Offload heavy processing to Web Workers without blocking the UI.
// rpc/video/index.ts
export type GenerateVideoFn = (timeline: Timeline) => Promise<Blob>;
export const generateVideo = irpc.declare<GenerateVideoFn>('generateVideo', () => new Blob());// rpc/video/constructor.ts
import { irpc } from '../lib/module.js';
import { generateVideo } from './index.js';
irpc.construct(generateVideo, async (timeline) => {
const ffmpeg = new FFmpeg();
await ffmpeg.load();
// ... processing
return videoBlob;
});// worker.ts
import { BroadcastRouter } from '@irpclib/broadcast/router';
import { irpc, transport } from './lib/module.js';
import './rpc/video/constructor.js';
const router = new BroadcastRouter(irpc, transport);// main.ts
import { generateVideo } from './rpc/video/index.js';
const video = await generateVideo(timeline);2. Multi-Tab Synchronization
Keep data synchronized across multiple tabs.
// rpc/cart/index.ts
export type UpdateCartFn = (items: CartItem[]) => Promise<void>;
export const updateCart = irpc.declare<UpdateCartFn>('updateCart', () => undefined);// rpc/cart/constructor.ts
import { irpc } from '../lib/module.js';
import { updateCart } from './index.js';
irpc.construct(updateCart, async (items) => {
cartStore.set(items);
});Tab 1 — Send updates:
import { updateCart } from './rpc/cart/index.js';
await updateCart(cartItems);Tab 2 — Receive updates:
import { BroadcastRouter } from '@irpclib/broadcast/router';
import { irpc, transport } from './lib/module.js';
import './rpc/cart/constructor.js';
const router = new BroadcastRouter(irpc, transport);3. Iframe Communication
Communicate between parent and child iframes.
// rpc/messaging/index.ts
export type SendMessageFn = (msg: string) => Promise<string>;
export const sendMessage = irpc.declare<SendMessageFn>('sendMessage', () => '');// rpc/messaging/constructor.ts
import { irpc } from '../lib/module.js';
import { sendMessage } from './index.js';
irpc.construct(sendMessage, async (msg) => {
return `Iframe received: ${msg}`;
});Parent window:
import { sendMessage } from './rpc/messaging/index.js';
const response = await sendMessage('Hello iframe');Iframe:
import { BroadcastRouter } from '@irpclib/broadcast/router';
import { irpc, transport } from './lib/module.js';
import './rpc/messaging/constructor.js';
const router = new BroadcastRouter(irpc, transport);4. Background Task Coordination
Coordinate background tasks across contexts.
// rpc/sync/index.ts
export type StartSyncFn = () => Promise<void>;
export const startSync = irpc.declare<StartSyncFn>('startSync', () => undefined);// rpc/sync/constructor.ts
import { irpc } from '../lib/module.js';
import { startSync } from './index.js';
let isSyncing = false;
irpc.construct(startSync, async () => {
if (isSyncing) return;
isSyncing = true;
try {
await syncWithServer();
} finally {
isSyncing = false;
}
});Tab 1 — Trigger sync:
import { startSync } from './rpc/sync/index.js';
await startSync();Tab 2 — Handle sync:
import { BroadcastRouter } from '@irpclib/broadcast/router';
import { irpc, transport } from './lib/module.js';
import './rpc/sync/constructor.js';
const router = new BroadcastRouter(irpc, transport);Performance Benefits
Zero Network Overhead
- No HTTP requests - Communication happens entirely in the browser
- No server required - Eliminates infrastructure costs
- Instant messaging - Direct browser-to-browser communication
Automatic Batching
Multiple simultaneous calls are batched into single BroadcastChannel messages:
// These calls are batched into 1 message
const [result1, result2, result3] = await Promise.all([
processTask1(),
processTask2(),
processTask3(),
]);Offline-First
Works completely offline since no server is required:
// Works without internet connection
const result = await heavyComputation(data);Error Handling
Network Errors
BroadcastChannel transport includes retry logic:
const transport = new BroadcastTransport({
channel: 'my-app',
maxRetries: 3,
retryMode: 'exponential', // 1s, 2s, 4s delays
retryDelay: 1000,
});Channel Closed
Calls fail immediately if the channel is closed:
try {
await someFunction();
} catch (error) {
if (error.message.includes('Invalid state')) {
// Channel was closed
console.error('BroadcastChannel is closed');
}
}Advanced Usage
Hooks
Unlike HTTP and WebSocket, BroadcastChannel has no incoming request object. The router injects only the internal AbortController by default. To supply the same standardized context keys used across other transports, pass initContext when calling resolve() directly.
router.resolve(requests, [
['token', self.workerToken],
]);
router.use(async () => {
const token = getContext<string>('token');
if (!token) throw new Error('Unauthorized');
setContext('user', await verifyToken(token));
});Custom Resolver
Provide a custom resolver for advanced request handling:
const router = new BroadcastRouter(irpc, transport, {
resolver: (req, module) => {
// Custom resolution logic
return new CustomResolver(req, module);
},
});Router Disconnect
The router tracks AbortController instances for every active stream. Call router.disconnect() to abort all active streams when the router is shut down:
// Abort all active streams and clean up
router.disconnect();Stream Cancellation
Clients can cancel individual streams by calling .close() on the RemoteState. The transport sends a cancellation packet to the router, which aborts the stream's AbortController and triggers cleanup:
const stream = watchData('key');
stream.start();
// Later — cancel this specific stream
stream.close();Multiple Channels
Use different channels for different purposes:
// Data sync channel
const dataTransport = new BroadcastTransport({
channel: 'data-sync'
});
// UI events channel
const uiTransport = new BroadcastTransport({
channel: 'ui-events'
});
// Use different transports for different packages
dataPackage.use(dataTransport);
uiPackage.use(uiTransport);Comparison with Other Transports
| Feature | BroadcastChannel | WebSocket | HTTP |
|---|---|---|---|
| Environment | Browser + Node.js Workers | Universal | Universal |
| Server Required | No | Yes | Yes |
| Cross-Tab | Yes | No | No |
| Web Workers | Yes | Yes | No |
| Latency | Lowest | Low | Medium |
| Offline | Yes | No | No |
| Setup Complexity | Simple | Medium | Simple |
Choose BroadcastChannel transport when:
- You're building browser applications with cross-tab communication
- You need to communicate with Web Workers (browser or Node.js)
- You want to coordinate between Node.js Worker Threads
- You want zero infrastructure costs
- You're building offline-first applications
Choose WebSocket transport when:
- You need server-side processing
- You're building real-time server communication
- You need cross-device synchronization
Choose HTTP transport when:
- You need maximum compatibility
- You're building REST-like APIs
- You have existing HTTP infrastructure
Browser Support
BroadcastChannel is supported in all modern browsers:
- Chrome 54+
- Firefox 38+
- Safari 15.4+
- Edge 79+
Not supported in:
- Internet Explorer
- Safari < 15.4
Check compatibility:
if ('BroadcastChannel' in window) {
// BroadcastChannel is supported
const transport = new BroadcastTransport({ channel: 'my-app' });
} else {
// Fallback to HTTP or WebSocket
const transport = new HttpTransport({ baseURL: '/api' });
}Troubleshooting
Messages Not Received
Problem: Messages sent but not received in other contexts
Solutions:
- Ensure all contexts use the same channel name
- Verify all contexts are on the same origin
- Check that router is created in receiving context
- Confirm BroadcastChannel is supported in the browser
Worker Communication Issues
Problem: Worker not receiving messages
Solutions:
- Verify worker script is loaded correctly
- Ensure BroadcastRouter is created in worker
- Check worker console for errors
- Confirm channel names match exactly
Performance Issues
Problem: Slow message delivery
Solutions:
- Reduce message size (BroadcastChannel has size limits)
- Use batching for multiple calls
- Avoid sending large binary data
- Consider using SharedArrayBuffer for large data
Security Considerations
Same-Origin Policy
BroadcastChannel only works within the same origin:
// These can communicate
// https://example.com/page1
// https://example.com/page2
// These cannot communicate
// https://example.com
// https://other.comData Privacy
All messages are visible to any context on the same origin:
// Don't send sensitive data without encryption
const transport = new BroadcastTransport({
channel: 'public-channel' // Visible to all tabs
});
// Encrypt sensitive data before sending
const encryptedData = await encrypt(sensitiveData);
await sendData(encryptedData);Next Steps
- Getting Started - Set up IRPC with BroadcastChannel transport
- WebSocket Transport - Server-based alternative
- HTTP Transport - REST-like alternative
- Specification - Full protocol details