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