BroadcastChannel transport
Two lines of setup: createBroadcastChannelTransport("tab-sync-demo") and
addTransport(transport). No handshake, no connection management — any tab that opens the
same-named channel is immediately in the mesh.
Tab Sync Dashboard shows how postal’s BroadcastChannel and ServiceWorker transports can run side-by-side on the same postal instance — each handling a different messaging boundary. Open it in two browser tabs side by side and watch state changes propagate in real time — login, theme, and preferences all stay in sync, while the ServiceWorker tracks exactly how many tabs are connected.
BroadcastChannel transport
Two lines of setup: createBroadcastChannelTransport("tab-sync-demo") and
addTransport(transport). No handshake, no connection management — any tab that opens the
same-named channel is immediately in the mesh.
ServiceWorker transport
Each tab opens a dedicated MessagePort to the ServiceWorker via connectToServiceWorker.
The SW uses this to track connected tab count and publish presence.updated events. This is
the key teaching point: two transports, one postal instance — BroadcastChannel for
tab-to-tab state, MessagePort for tab-to-SW presence tracking.
The production pattern
localStorage holds the authoritative state. A new tab hydrates from storage via
loadState() and renders immediately. Live changes flow through postal so every open tab
stays consistent without polling or manual coordination.
Echo prevention in postal core
Outbound envelopes are stamped with source: instanceId in transport.ts. Inbound
envelopes matching the local instance are silently dropped. User actions just publish —
subscribers handle all state mutation regardless of whether the message was local or remote.
Wiretap observability
addWiretap() sees every message on the bus — both local publishes and messages arriving
from other tabs via either transport. The activity log in the demo is 100% wiretap with no
duplication from the subscribers.
Open http://localhost:5173 in two or more tabs.
Try:
The full source is in examples/tab-sync/ in the postal.js repository.
Key files:
src/main.ts — all postal wiring: transport, channel, subscribers, user action publishes, wiretapsrc/state.ts — AppState type and localStorage helpers (loadState, saveState)src/ui.ts — DOM layer with no postal imports; all state changes flow through callbacks