6.3 KiB
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<T,R>() 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
class TypedBroadcastChannel extends EventEmitter<BroadcastChannelEventMap> {
constructor(config: ChannelConfig)
send<T>(type: string, payload: T): void
on<T>(type: string, handler: ChannelHandler<T>): () => void
ask<T, R>(type: string, payload: T, options?: AskOptions): Promise<R> // 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<T, R>() 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
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.