Skip to content

Broadcast buses and messengers

BroadcastTypedEventBus<EVENT> decorates a local TypedEventBus<EVENT> with cross-context delivery. The transport is notification-only: there is no acknowledgment, remote completion wait, replay, or delivery guarantee.

Keep the delegate when constructing a broadcast bus so its handlers can be cleaned up separately. A supplied messenger is closed by destroy() too; sharing that messenger with an unrelated bus transfers overlapping ownership and can terminate the other consumer. Use a separate messenger per independently owned broadcast bus.

Broadcast options and flow

new BroadcastTypedEventBus(options: BroadcastTypedEventBusOptions<EVENT>) requires delegate. Its type and handlers come from the delegate; on/off forward to it. Default messenger is createCrossTabMessenger('_broadcast_:' + delegate.type); construction throws Error('Messenger setup failed') if none is available.

Option/memberDefaultContract
messenger?: CrossTabMessengerRuntime-selected transportInject to control environment/channel ownership.
messageTransformer.serialize(event)No transformerConvert outbound event into wire data.
messageTransformer.deserialize(message)No transformerDecode incoming wire data into EVENT.
serializeBeforeDispatch?falseTrue snapshots before local handlers; false serializes after local delivery.
fallbackSerialize?(message, error)NoneOn a postMessage throw, transform once and retry postMessage once.
destroy()Closes messenger only; does not destroy delegate or remove its handlers.

emit awaits the local delegate first, then posts the message (with optional pre-serialization). Outbound serialization/post failures reject emit; local delivery may already have happened. A pre-serialization throw prevents local delivery. Incoming messages deserialize and emit only on the delegate, so they are not rebroadcast; decode/delegate rejections are caught and warned. The mutable messageTransformer affects later operations.

CrossTabMessenger

The contract is postMessage(message: any): void, a setter onmessage: CrossTabMessageHandler, and close(): void; handler type is (message: any) => void. Setting onmessage replaces the callback.

isBroadcastChannelSupported() checks the global and prototype postMessage. isStorageEventSupported() checks StorageEvent, window.addEventListener, and localStorage or sessionStorage availability. These are feature probes, not permission tests. createCrossTabMessenger(channelName) prefers BroadcastChannelMessenger, then StorageMessenger, otherwise returns undefined. Construction errors are not silently converted to fallback.

new BroadcastChannelMessenger(channelName) wraps native BroadcastChannel, forwards MessageEvent.data, and uses structured cloning; unsupported data can throw DataCloneError. close() closes its channel.

StorageMessenger

new StorageMessenger(options: StorageMessengerOptions) requires a browser with window and localStorage even when injecting a backend. Options are required channelName, optional storage (localStorage), ttl (1000 ms), and cleanupInterval (60000 ms).

Each post JSON-encodes StorageMessage {data: any, timestamp: number} under a unique channel-prefixed storage key, then schedules key deletion after ttl. Periodic cleanup removes expired/invalid messages matching that channel. Receiving filters by storageArea and key format; invalid JSON warns. TTL controls cleanup, not reliable replay or a receive-age filter. Native storage events do not notify their originating document.

close() removes the storage listener and clears interval/pending deletion timers; already-written keys are not all removed immediately. JSON stringify, quota, or access failures can throw on posting. A sessionStorage backend has that platform's restricted sharing scope. The sender's local delegate still handles local delivery independently.

Complete example

ts
import {
  BroadcastTypedEventBus,
  SerialTypedEventBus,
  createCrossTabMessenger,
} from '@ahoo-wang/fetcher-eventbus';

const messenger = createCrossTabMessenger('settings-demo');
if (messenger) {
  const delegate = new SerialTypedEventBus<{ theme: string }>('settings');
  const bus = new BroadcastTypedEventBus({ delegate, messenger });
  bus.on({
    name: 'ui',
    handle: event => {
      console.log(event.theme);
    },
  });
  try {
    await bus.emit({ theme: 'dark' });
  } finally {
    bus.destroy();
    delegate.destroy();
  }
}

Public symbols and source

SymbolImplementation
BroadcastTypedEventBusOptionsbroadcastTypedEventBus.ts:24
BroadcastTypedEventBusbroadcastTypedEventBus.ts:121
BroadcastChannelMessengerbroadcastChannelMessenger.ts:19
CrossTabMessageHandlercrossTabMessenger.ts:17
CrossTabMessengercrossTabMessenger.ts:25
isBroadcastChannelSupportedcrossTabMessenger.ts:46
isStorageEventSupportedcrossTabMessenger.ts:53
createCrossTabMessengercrossTabMessenger.ts:63
StorageMessengerOptionsstorageMessenger.ts:19
StorageMessagestorageMessenger.ts:27
StorageMessengerstorageMessenger.ts:35

Package index

Released under the Apache License 2.0.