Elects a unique leader among multiple tabs/clients of the same origin.
Module leaderElection | Source packages/front/fw/src/dom/utils/leaderElection.js | Deps broadcastChannel | Worker-safe no
Used to centralise a WebSocket connection, relay cross-tab via a single writer, avoid job duplication (remote sync, cron), or as a fallback when SharedWorker is unavailable.
Two automatic strategies:
- Primary — Web Locks API:
navigator.locks.request(name, {mode: 'exclusive'}, callback). The client holding the lock is the leader as long as it lives; closing the tab releases the lock automatically, triggering the next election without delay. - Fallback — BroadcastChannel + heartbeat: if Web Locks is unavailable, probabilistic election. The lowest id wins. Each client emits a heartbeat every
heartbeatMsms; if no heartbeat from a lower id is received withintakeoverMsms, the client self-promotes and notifies the others.
Resolve
const leaderElection = runtime.resolve('leaderElection');
// Returns: { create, support }
API
| Method | Signature | Returns |
|---|---|---|
create |
(opts: CreateOpts) => Election |
Election instance |
support |
() => SupportInfo |
Environment capabilities |
leaderElection.create(opts)
Options create
| Option | Type | Default | Description |
|---|---|---|---|
channel |
string |
— | Required. Web Locks lock name and BroadcastChannel name (must be consistent). |
id |
string |
UUID v4 | Unique client identifier. |
heartbeatMs |
number |
1000 |
Heartbeat interval (fallback only). |
takeoverMs |
number |
3000 |
Delay before takeover without lower heartbeat (fallback only). |
Returned Election object
| Member | Signature | Description |
|---|---|---|
id |
string |
Identifier of this instance. |
onLeader |
(fn: () => void) => unsubscribe |
Called when this instance becomes leader. |
onFollower |
(fn: () => void) => unsubscribe |
Called when this instance loses leadership. |
isLeader |
() => boolean |
true if this instance is leader. |
leader |
() => {id, since, isMe} | null |
Information about the current leader. |
dispose |
() => void |
Releases all resources. Idempotent. |
leaderElection.support()
// { webLocks: boolean, broadcastChannel: boolean }
leaderElection.support();
Returns capabilities available in the current environment. webLocks: true indicates the primary strategy will be used.
Examples
Singleton WebSocket connection
const leaderElection = runtime.resolve('leaderElection');
const election = leaderElection.create({ channel: 'ws-leader' });
election.onLeader(() => {
const socket = new WebSocket('wss://api.example.com/stream');
socket.onmessage = (e) => broadcastToAllTabs(e.data);
});
election.onFollower(() => {
// Close the connection if leadership is lost
socket?.close();
});
SharedWorker fallback
const leaderElection = runtime.resolve('leaderElection');
const { webLocks, broadcastChannel } = leaderElection.support();
if (!sharedWorkerAvailable && broadcastChannel) {
const election = leaderElection.create({
channel: 'sync-leader',
heartbeatMs: 500,
takeoverMs: 1500,
});
election.onLeader(() => startSyncJob());
election.onFollower(() => stopSyncJob());
}
Singleton sync
const election = leaderElection.create({ channel: 'cron-sync' });
election.onLeader(async () => {
while (election.isLeader()) {
await runSyncTask();
await sleep(30_000);
}
});
Notes
- In Web Locks mode, the leadership transition is instantaneous (atomic lock release) — no heartbeat required.
- In fallback mode, the maximum delay before taking leadership is
takeoverMs + heartbeatMs. Decreasing these values reduces latency but increases BroadcastChannel traffic. dispose()is idempotent: calling it multiple times produces no error.- The
_forceFallback: trueoption is reserved for tests; it forces BroadcastChannel mode even when Web Locks is available. leader()may returnnullbriefly during the initial election phase.
See also
- broadcastChannel — BroadcastChannel transport used in fallback
- fsAccess — shared cross-tab storage (OPFS)
- Guide module-pattern