Lesson 0002 · ~12 minutes
One skill: wire a named
BroadcastChannel correctly — construct, listen, post
(with local side effects), and close — including
sender-excluded delivery and structured-clone limits.
You can sketch a two-tab logout or theme fan-out in a few lines and correctly predict who receives the message — without re-litigating SharedWorker vs BC.
Create a
BroadcastChannel
with a string name. The name is how peers find each other — same origin
partitioning +
exact same name
=
one bus
(HTML §9.5).
const auth = new BroadcastChannel("auth");
console.log(auth.name); // "auth" — read-only after construction
// Different name ⇒ different bus (silent isolation, not an error)
const theme = new BroadcastChannel("theme");
[Exposed=(Window,Worker)]).
"myapp:auth").
Incoming messages are
message events on the channel object (a
MessageEvent). Use onmessage or
addEventListener("message", …). Payload is in
event.data; event.origin is the poster’s
origin.
const auth = new BroadcastChannel("auth");
auth.onmessage = (event) => {
if (event.data === "logout" || event.data?.type === "logout") {
showLoggedOut();
}
};
function logoutRequested() {
// 1) Local side effects first — you will NOT get your own message
doLogout();
showLoggedOut();
// 2) Fan-out to other same-origin contexts on this channel
auth.postMessage("logout");
}
That shape matches the
HTML logout example. Objects work too:
auth.postMessage({ type: "logout", at: Date.now() }).
Spec algorithm: build the destination list, then
remove the source channel. Other tabs with
new BroadcastChannel("auth") hear you; your own
onmessage does not fire for that post. Always update
local UI (or re-enter a shared handler) yourself.
postMessage(message) runs
structured serialize
only. Unlike
worker.postMessage(msg, [transfer]), BroadcastChannel has
no transfer parameter
(IDL in §9.5). Receivers get a
clone, not
ownership of a transferable.
Date, etc.).
DataCloneError.
If deserialize fails at a destination,
messageerror fires there instead of
message.
close()
sets the closed flag. Further
postMessage on that object throws
InvalidStateError. The Standard also notes: leave a channel
open with a message listener and it can look like a leak — the global
keeps a strong reference while listeners exist
(§9.5). Close when a feature unmounts; tab/worker teardown ends
participation too.
auth.close();
// auth.postMessage("logout"); // InvalidStateError
// Prefer one long-lived channel per name in an app shell, or close on cleanup:
// useEffect(() => { const ch = new BroadcastChannel("theme"); …; return () => ch.close(); }, []);
One channel name per concern → onmessage switch on
type → local apply then post →
close() when the owner goes away. Keep payloads small;
never assume the bus is durable storage.
| Call | Role |
|---|---|
new BroadcastChannel(name) |
Join / create the named bus for this context |
name |
Read-only channel name |
postMessage(message) |
Clone to other same-name channels; not to self |
onmessage / message |
Receive; data is the clone |
onmessageerror |
Clone/deserialize failure at this destination |
close() |
Detach; further posts throw |
Equal-length options. Think like a code review on multi-tab messaging.
Tab A posts "logout" on channel
"auth". Tab A also has
auth.onmessage registered. What should Tab A do for its
own UI?
Tab A uses new BroadcastChannel("auth"). Tab B uses
new BroadcastChannel("Auth") (capital A). Same origin.
You want to send a large ArrayBuffer to other tabs
without copying, the way
worker.postMessage(buf, [buf]) transfers ownership.
A React effect closed its BroadcastChannel on unmount.
Later code calls postMessage on that same object.
Theme toggle: persist preference and update every open same-origin tab’s chrome. Prefer the lightest correct wiring.
new BroadcastChannel(name) + matching
name + same origin partitioning = bus membership.
close() when done; posting on a closed channel throws
InvalidStateError.
MDN — BroadcastChannel for the interface surface, then the normative steps in HTML §9.5 (destination list, sender removal, serialize, close flag). Keep core-api.html open while you practice.