# OpenSpec Change: enterprise-browser-broadcast-channel - **Change ID**: enterprise-browser-broadcast-channel - **Package**: `@suaveplan/broadcast-channel` - **Category**: browser - **Target path**: `browser/platform/broadcast-channel` - **Status**: IN PROGRESS - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/broadcast-channel.md` --- ## Summary Deliver `@suaveplan/broadcast-channel` on the genesis branch at enterprise quality. The package provides `TypedBroadcastChannel` with message routing, tab presence tracking, heartbeats, stale-tab cleanup, and leader election. Genesis adds: `localStorage`-backed fallback transport (Safari < 15.4), Zod runtime validation on incoming frames, typed `MessageEvent` (removes `any`), `ask()` request/response primitive, enhanced leader-election strategies, payload-size guard, shared ID generator, and OTel metrics. --- ## Current-state audit (genesis) Fresh build — does not exist on genesis yet. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/events` (`workspace:^`) | `EventEmitter` base for lifecycle events | | `@suaveplan/error` (`workspace:^`) | `CHANNEL_CLOSED`, `MESSAGE_MALFORMED`, `PAYLOAD_TOO_LARGE` | | `@suaveplan/types` (`workspace:^`) | `LoggerLike` | | `@suaveplan/logger` (`workspace:^`) | Structured logging | | `@suaveplan/crypto` (`workspace:^`) | `generateId()` (replaces duplicate local implementations) | | `@suaveplan/schema-registry` (`workspace:^`) | `ChannelMessage` schema | | `zod` (`catalog:forms`) | `ChannelMessageSchema` | | `@opentelemetry/api` (`catalog:observability`) | Message counters, handler latency | | `tslib` (`catalog:build`) | Runtime helpers | Dev: `vitest`, `@vitest/coverage-istanbul`, `happy-dom`, `@playwright/test`, `@suaveplan/testing`. --- ## Preserved public API surface ```ts class TypedBroadcastChannel extends EventEmitter { constructor(config: ChannelConfig) send(type: string, payload: T): void on(type: string, handler: ChannelHandler): () => void ask(type: string, payload: T, options?: AskOptions): Promise // new close(): void getTabId(): string getLeaderId(): string | null getTabs(): TabInfo[] } // Transport seam (new) interface ChannelTransport { send(msg: ChannelMessage): void; onMessage(cb: (msg: ChannelMessage) => void): () => void; close(): void } class BroadcastTransport implements ChannelTransport { constructor(name: string) } class LocalStorageTransport implements ChannelTransport { constructor(name: string) } // fallback // Leader strategies (new) interface LeaderStrategy { elect(tabs: TabInfo[]): string } class LowestIdStrategy implements LeaderStrategy { } class ActiveTabStrategy implements LeaderStrategy { } // visibilitychange-aware // Types interface ChannelConfig { name: string; heartbeatInterval?: number; staleThreshold?: number; maxMessageBytes?: number; transport?: ChannelTransport; leaderStrategy?: LeaderStrategy } interface ChannelMessage { id: string; type: string; tabId: string; payload: unknown; timestamp: number } interface TabInfo { id: string; joinedAt: number; active: boolean; leader: boolean } ``` Subpath exports: `"."`, `"./transport"`, `"./leader"`, `"./types"`. --- ## New additions for genesis ### 1. `localStorage` fallback transport `LocalStorageTransport` implements `ChannelTransport` using `localStorage` + `storage` events; auto-selected when `BroadcastChannel` API is absent (Safari < 15.4). ### 2. Zod runtime validation `ChannelMessageSchema` validates every incoming frame in `handleIncoming`; malformed frames are dropped and `error` event emitted with `MESSAGE_MALFORMED`. ### 3. Typed `MessageEvent` `ChannelLike.addEventListener` typed to `(event: MessageEvent) => void` — no `any`. ### 4. `ask()` request/response Correlation-ID request/response layered on `send`/`on`; configurable timeout; reject on channel close. ### 5. Pluggable leader-election strategies `LowestIdStrategy` (default), `ActiveTabStrategy` (prefers the currently visible tab via `visibilitychange`). Custom strategies via `LeaderStrategy` interface. ### 6. Payload-size guard `send()` throws `PAYLOAD_TOO_LARGE` when serialized payload exceeds `maxMessageBytes` (default 1 MB). ### 7. Shared ID generator `src/internal/id.ts` using `@suaveplan/crypto`; replaces the two duplicate `generateTabId`/`generateMsgId` in the original. ### 8. Error codes `CHANNEL_CLOSED`, `MESSAGE_MALFORMED`, `PAYLOAD_TOO_LARGE`. ### 9. OTel metrics Counter `channel.messages_sent_total`, counter `channel.messages_received_total`, counter `channel.messages_dropped_total`, gauge `channel.tabs_tracked`, histogram `channel.handler_latency_ms`. --- ## File layout ```text browser/platform/broadcast-channel/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts channel/ channel/ channel.ts (TypedBroadcastChannel, ≤250 lines) channel.test.ts channel.md index.ts transport/ broadcast/ broadcast.ts broadcast.test.ts broadcast.md index.ts local-storage/ local-storage.ts local-storage.test.ts local-storage.md index.ts ask/ ask/ ask.ts ask.test.ts ask.md index.ts leader/ leader/ leader.ts (LeaderStrategy interface + LowestIdStrategy + ActiveTabStrategy) leader.test.ts leader.md index.ts schema/ channel-message-schema/ channel-message-schema.ts channel-message-schema.md index.ts internal/ id.ts (generateId via @suaveplan/crypto) types/ broadcast-channel.types.ts broadcast-channel.types.md constants/ index.ts tests/ integration.test.ts e2e/ broadcast-channel.spec.ts (cross-tab: Chromium; fallback transport: WebKit) ``` --- ## Impact **Downstream**: `@suaveplan/cache-browser`, `@suaveplan/storage-browser`, `@suaveplan/analytics-browser`, any package using cross-tab communication. **Breaking changes**: None — all additions are additive. `ChannelLike` type tightened (removes `any`); consumers may need to update handler signatures. **Prerequisites**: `@suaveplan/events`, `@suaveplan/error`, `@suaveplan/crypto` on Verdaccio.