# @suaveplan/broadcast-channel - Typed cross-tab messaging built on the BroadcastChannel API — leader election, tab presence, Zod-validated boundaries, OpenTelemetry-instrumented hot paths, StorageLike-backed fallback for Safari < 15.4. Browser-tier. Typed cross-tab messaging built on the Web `BroadcastChannel` API. Browser-tier package — leader election, tab presence and heartbeats, message-type handler registry, Zod-validated boundaries, OpenTelemetry-instrumented hot paths, deterministic `Clock` / `Rng` / `LoggerLike` injection, and a `StorageLike`-backed fallback transport for Safari < 15.4 (and any sandbox where the native `BroadcastChannel` is unavailable). ## Documentation tree - [ask](./src/ask/ask.md) — `AskController` is the request-side engine for the cross-tab request/response primitive layered on top of `TypedBroadca… - [ask](./src/ask/ask.md) — `AskController` is the request-side engine for the cross-tab request/response primitive layered on top of `TypedBroadca… - [responder](./src/ask/responder.md) — `AskResponderRegistry` is the response-side counterpart to `AskController`. It maps an inbound `handlerKey` to the sing… - [channel](./src/channel/channel.md) — `TypedBroadcastChannel` is the high-level cross-tab messaging object exported by `@suaveplan/broadcast-channel`. It wra… - [channel](./src/channel/channel.md) — `TypedBroadcastChannel` is the high-level cross-tab messaging object exported by `@suaveplan/broadcast-channel`. It wra… - [heartbeat](./src/channel/heartbeat.md) — `HeartbeatRunner` is a tiny lifecycle helper that owns the pair of `setInterval` timers a `TypedBroadcastChannel` needs… - [errors](./src/errors/codes.md) — This module declares the stable error-code registry for `@suaveplan/broadcast-channel`. Every error the package throws… - [codes](./src/errors/codes.md) — This module declares the stable error-code registry for `@suaveplan/broadcast-channel`. Every error the package throws… - [id](./src/id/id.md) — `generateId` is the shared 16-character lowercase-hex identifier generator used for tab IDs and per-message IDs through… - [id](./src/id/id.md) — `generateId` is the shared 16-character lowercase-hex identifier generator used for tab IDs and per-message IDs through… - [logger](./src/logger/noop.md) — `noopLogger` is a shared zero-cost `LoggerLike` implementation used as the default by `TypedBroadcastChannel` and by `c… - [noop](./src/logger/noop.md) — `noopLogger` is a shared zero-cost `LoggerLike` implementation used as the default by `TypedBroadcastChannel` and by `c… - [otel](./src/otel/metrics.md) — This module declares the six OpenTelemetry counter instruments emitted by `@suaveplan/broadcast-channel`. Every counter… - [metrics](./src/otel/metrics.md) — This module declares the six OpenTelemetry counter instruments emitted by `@suaveplan/broadcast-channel`. Every counter… - [schemas](./src/schemas/messages.md) — This module declares the Zod boundary schemas for `@suaveplan/broadcast-channel`. `ChannelMessageSchema` validates ever… - [messages](./src/schemas/messages.md) — This module declares the Zod boundary schemas for `@suaveplan/broadcast-channel`. `ChannelMessageSchema` validates ever… - [local-storage](./src/transport/local-storage.md) — `createLocalStorageChannelFactory` is the `StorageLike`-backed fallback transport. Use it whenever the host lacks a nat… - [native](./src/transport/native.md) — `nativeChannelFactory` is a thin adapter that exposes the global `BroadcastChannel` constructor as a `ChannelLike`. Use… ## Install ```bash bun add @suaveplan/broadcast-channel ``` ## Quick start ```ts import { nativeChannelFactory, TypedBroadcastChannel, } from "@suaveplan/broadcast-channel"; const channel = new TypedBroadcastChannel({ config: { name: "app-bus" }, factory: nativeChannelFactory, }); channel.open(); channel.on<{ userId: string }>("user:signed-in", (msg) => { console.log(`peer tab ${msg.senderId} signed in as ${msg.payload.userId}`); }); channel.send("user:signed-in", { userId: "u_123" }); ``` ## Fallback transport (Safari < 15.4) ```ts import { createLocalStorageChannelFactory, isNativeBroadcastChannelAvailable, nativeChannelFactory, TypedBroadcastChannel, } from "@suaveplan/broadcast-channel"; import { yourStorageLike } from "./storage"; const factory = isNativeBroadcastChannelAvailable() ? nativeChannelFactory : createLocalStorageChannelFactory(yourStorageLike, { pollIntervalMs: 50, retentionMs: 30_000, }); const channel = new TypedBroadcastChannel({ config: { name: "app-bus" }, factory, }); ``` ## Modules - [`channel/channel`](./src/channel/channel.md) — `TypedBroadcastChannel` core. - [`channel/heartbeat`](./src/channel/heartbeat.md) — `HeartbeatRunner` interval pair. - [`errors/codes`](./src/errors/codes.md) — `BROADCAST_CHANNEL_CODES` registry + four typed error subclasses. - [`id/id`](./src/id/id.md) — `generateId(rng)` shared 16-hex id generator. - [`logger/noop`](./src/logger/noop.md) — shared no-op `LoggerLike` default. - [`otel/metrics`](./src/otel/metrics.md) — six OpenTelemetry counters. - [`schemas/messages`](./src/schemas/messages.md) — Zod boundary schemas. - [`transport/native`](./src/transport/native.md) — native `BroadcastChannel` adapter. - [`transport/local-storage`](./src/transport/local-storage.md) — `StorageLike`-backed fallback. - [`types/broadcast-channel`](./src/types/broadcast-channel.types.ts) — public shapes. ## Error codes | Code | When | |---|---| | `BROADCAST_CHANNEL.CHANNEL_CLOSED` | `send()` called before `open()` or after `close()` | | `BROADCAST_CHANNEL.MESSAGE_MALFORMED` | inbound frame fails `ChannelMessageSchema` | | `BROADCAST_CHANNEL.PAYLOAD_TOO_LARGE` | outbound payload exceeds `config.maxMessageBytes` (default 1 MiB) | | `BROADCAST_CHANNEL.STORAGE_FAILED` | fallback-transport `StorageLike` get/set/list/delete rejected | ## OpenTelemetry Six counters under meter `@suaveplan/broadcast-channel@0.1.0`: - `channel.message.sent` (`{ type, channel }`) - `channel.message.received` (`{ type, channel }`) - `channel.message.dropped` (`{ reason, channel }`) - `channel.tab.joined` (`{ channel }`) - `channel.tab.left` (`{ channel }`) - `channel.leader.elected` (`{ channel }`) ## License MIT