UNPKG

leader-tab

Version:

An algorithm for selecting a leader from all the open tabs of a website.

80 lines (79 loc) 2.33 kB
// src/index.ts var defaultParams = { channelName: "leader-election", heartbeatInterval: 50, deathTimeout: 500 }; function elector({ onLeaderElected, onLeaderDemoted } = { onLeaderElected: () => { }, onLeaderDemoted: () => { } }, { channelName = defaultParams.channelName, heartbeatInterval = defaultParams.heartbeatInterval, deathTimeout = defaultParams.deathTimeout } = defaultParams) { const tabId = crypto.randomUUID(); const channel = new BroadcastChannel(channelName); const knownTabs = /* @__PURE__ */ new Map(); let isLeader = false; function heartbeat() { channel.postMessage({ type: "heartbeat", tabId }); knownTabs.set(tabId, Date.now()); electLeader(); } function cleanup() { const now = Date.now(); for (const [id, lastSeen] of knownTabs.entries()) { if (now - lastSeen > deathTimeout) { knownTabs.delete(id); } } electLeader(); } function electLeader() { const sorted = [...knownTabs.keys()].sort(); const newLeader = sorted[0]; const wasLeader = isLeader; isLeader = newLeader === tabId; if (isLeader && !wasLeader) { onLeaderElected(); } else if (!isLeader && wasLeader) { onLeaderDemoted(); } } function handleMessage(event) { const msg = event.data; if (msg.type === "heartbeat") { knownTabs.set(msg.tabId, Date.now()); } else if (msg.type === "goodbye") { knownTabs.delete(msg.tabId); } } channel.addEventListener("message", handleMessage); function sendGoodbye() { channel.postMessage({ type: "goodbye", tabId }); } if (typeof window !== "undefined" && window.addEventListener) { window.addEventListener("beforeunload", sendGoodbye); } const heartbeatIntervalId = setInterval(heartbeat, heartbeatInterval); const cleanupIntervalId = setInterval(cleanup, heartbeatInterval); return Object.freeze({ isLeader: () => isLeader, getTabId: () => tabId, destroy: () => { clearInterval(heartbeatIntervalId); clearInterval(cleanupIntervalId); channel.removeEventListener("message", handleMessage); if (typeof window !== "undefined" && window.removeEventListener) { window.removeEventListener("beforeunload", sendGoodbye); } sendGoodbye(); } }); } export { elector };