UNPKG

leader-tab

Version:

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

105 lines (103 loc) 3.33 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { elector: () => elector }); module.exports = __toCommonJS(index_exports); 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(); } }); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { elector });