broadcast-channel

js/globals/broadcast-channel.ts

BroadcastChannel global for one-to-many pub/sub across Realms.

Channels are addressed purely by name: every BroadcastChannel constructed with the same name — in this Realm, in a sibling Realm on the same thread, or in a thread/process Realm with its own Isolate — receives every message posted to that name, except that a sender never receives its own message.

Delivery is always asynchronous and takes one of two paths. Peers in the same Realm are handed the serialized bytes through an in-realm task queue flushed on a subsequent event-loop turn. Peers in other Realms receive them through the Rust-side global registry (internal:broadcast), which fans the bytes out to every subscriber on the channel name and signals each one over a wake pipe registered with loop.readable. Registry echoes of a Realm's own messages carry the sending Realm's id and are silently dropped on receipt, so each peer sees each message exactly once.

BroadcastChannel does not accept a transfer list. Messages are serialized through the runtime serializer, so functions, symbols, weak collections, and other unsupported structured-clone values fail synchronously in postMessage() with a DataCloneError. Deserialization failures from a peer are reported as messageerror events with data === null.

BroadcastChannel is installed as a global — no import is needed. Each instance holds a live registry subscription until close() is called (or a using declaration disposes it), so long-lived programs should close channels they no longer need.

Example

const channel = new BroadcastChannel('cache-invalidations');
channel.onmessage = (event) => {
  console.log('invalidate', event.data.key);
};

channel.postMessage({ key: 'users:42' });
channel.close();

HTML BroadcastChannel API: https://html.spec.whatwg.org/multipage/web-messaging.html#broadcasting-to-other-browsing-contexts

Classes

class BroadcastChannel extends EventTarget {

One-to-many channel scoped by name across Fino Realms and Isolates.

Constructing an instance subscribes it to the named channel; every other live, unclosed subscriber with the same name receives messages posted to it, delivered asynchronously as message events. The sender never receives its own message. Payloads that a peer fails to deserialize surface as messageerror events with data === null instead.

Instances are EventTargets, so addEventListener('message', ...) works alongside the onmessage / onmessageerror handler properties. Each instance owns a registry subscription and a wake-pipe watcher until close() runs; the class also implements Symbol.dispose, so a using declaration closes it automatically at end of scope.

const channel = new BroadcastChannel('updates');
channel.onmessage = (event) => console.log(event.data);
channel.postMessage({ ok: true });
channel.close();

Getters

get onmessage()

Handler invoked for successfully deserialized message events.

Assigning a function registers it as an ordinary message listener, so it runs in registration order relative to addEventListener('message', ...) listeners and is skipped if an earlier listener calls stopImmediatePropagation(). Assigning null (or any non-function) clears it; reassigning replaces the previous handler.

const bc = new BroadcastChannel('events');
bc.onmessage = (event) => console.log(event.data);
get onmessageerror()

Handler invoked when received bytes cannot be deserialized.

The MessageEvent dispatched for these failures has data === null — the original payload is unrecoverable. This is most likely when serializer versions disagree across Isolates or a raw publisher sends malformed bytes. Assignment semantics match onmessage: the handler is a regular messageerror listener, and null clears it.

const bc = new BroadcastChannel('events');
bc.onmessageerror = () => console.warn('dropped an undecodable message');
get name(): string

Channel name used for subscription and publishing.

Read-only; this is the string-coerced constructor argument and remains available after close().

new BroadcastChannel(123 as any).name; // "123"

Setters

set onmessage(fn: ((ev: MessageEvent) => void) | null)
set onmessageerror(fn: ((ev: MessageEvent) => void) | null)

Constructors

constructor(name: string)

Subscribe to a named broadcast channel.

The name is string-coerced and matched exactly against peer channel names. Construction registers a Rust-side subscriber and starts an asynchronous receive loop on the next microtask, so messages published by peers can arrive as soon as the current turn yields to the event loop; the loop and the subscription are cleaned up by close().

Throws a TypeError if called with no arguments.

const bc = new BroadcastChannel('cache-invalidations');
bc.name; // "cache-invalidations"

Methods

postMessage(message: unknown): void

Publish a structured-clone-serializable message to peer subscribers.

The message is serialized once, synchronously, then fanned out to every other unclosed channel with the same name — same-Realm peers via the local task queue, cross-Realm peers via the Rust registry. Delivery is always asynchronous, and this channel never receives its own message. Mutating the original value after postMessage() returns cannot affect what peers observe.

Transfer lists are not supported by BroadcastChannel. Throws InvalidStateError if the channel is closed, TypeError if called with no arguments, and DataCloneError if the value cannot be serialized (functions, symbols, weak collections, and other unsupported structured-clone values).

const bc = new BroadcastChannel('jobs');
bc.postMessage({ id: 1, state: 'ready' });
close(): void

Close the channel and unregister the subscriber.

After close the channel stops receiving: messages already in flight are discarded on arrival, no further message or messageerror events fire, and any subsequent postMessage() throws InvalidStateError. The receive loop is woken so it can remove its read watcher and unsubscribe from the Rust registry. The operation is idempotent — extra calls are no-ops.

const bc = new BroadcastChannel('jobs');
bc.close();
bc.close(); // no-op