UNPKG

@liuxb001/master-tab-coordinator

Version:

MasterTabCoordinator:用于多标签页间通信与协调的TypeScript库

951 lines 39.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MasterTabCoordinator = void 0; const TAG = "MasterTabCoordinator"; /** * MasterTabCoordinator - 主从标签管理类,在多标签环境中,选出一个主标签 * * 支持功能: * - 检测标签页打开/关闭/隐藏/显示 * - 主从标签页选举(基于页面活动时间) * - 单实例应用模式 * - 标签页间消息传递 * - 状态同步 * - 心跳停止(当长时间无其他标签页活动时) */ //NOTE: 可能会出现短时间内没有master的情况(master被关闭后,要等待心跳超时才能重新选出master) class MasterTabCoordinator { /** * 获取 TabManager 实例(单例模式) */ static getInstance(options) { if (!MasterTabCoordinator.instance) { MasterTabCoordinator.instance = new MasterTabCoordinator(options); } else if (options) { // 更新现有实例的选项 MasterTabCoordinator.instance.updateOptions(options); } return MasterTabCoordinator.instance; } /** * 私有构造函数,防止直接创建实例 */ constructor(options) { this.channel = null; this.isClosing = false; // 标签页状态 this.isMaster = true; // 默认初始就是master this.isHidden = document.hidden; this.isInitialized = false; this.masterTabId = null; this.lastActiveTime = Date.now(); // 最后活动时间 // 心跳管理相关 this.lastHeartbeatReceived = 0; // 上次接收到外部心跳的时间 this.heartbeatInterval = null; // 心跳间隔 this._isHeartbeatStopped = false; // 当前是否已停止心跳 this.heartbeatMode = "active"; // 心跳模式 this.otherTabsDetected = false; // 是否检测到其他标签页 this.heartbeatStopTimeout = null; // 停止心跳的计时器 this.firstHeartbeatAfterRestore = false; // 恢复心跳后的第一次心跳 // 缓存已知标签页 this.knownTabsCache = new Map(); // 定时器 this.cleanupTimeout = null; this.masterElectionTimeout = null; /** * 更新活动时间 */ this.updateActivityTime = () => { const now = Date.now(); this.lastActiveTime = now; // 更新自己在缓存中的最后活动时间 const tabInfo = this.knownTabsCache.get(this.tabId); if (tabInfo) { tabInfo.lastActive = now; this.setTabInfo(this.tabId, tabInfo); } // 注意:根据需求,不再因为用户活动自动恢复心跳 // 只有当其他标签页发送消息时才恢复心跳 // 如果心跳没有停止,并且标签页可见且不在关闭过程中,则发送一次心跳 if (!this._isHeartbeatStopped && !this.isHidden && this.channel && !this.isClosing) { // 发送一次心跳,通知其他标签页当前活动状态 this.sendHeartbeat(); } }; /** * 处理页面可见性变化 */ this.handleVisibilityChange = () => { const isCurrentlyHidden = document.hidden; if (this.isHidden !== isCurrentlyHidden) { this.isHidden = isCurrentlyHidden; // 更新最后活动时间 this.lastActiveTime = Date.now(); // 更新标签页可见性状态和活动时间 const tabInfo = this.knownTabsCache.get(this.tabId); if (tabInfo) { tabInfo.isHidden = isCurrentlyHidden; tabInfo.lastActive = this.lastActiveTime; this.setTabInfo(this.tabId, tabInfo); } // 发送可见性变化消息 - 即使心跳已停止,也发送此重要消息 this.sendMessage(isCurrentlyHidden ? "tab-hidden" : "tab-visible", { lastActive: this.lastActiveTime, }); // 如果是从标签页且不在心跳停止状态,请求状态同步 if (!isCurrentlyHidden && !this.isMaster && !this._isHeartbeatStopped) { setTimeout(this.requestStateSync.bind(this), 300); } // 标签页可见性变化会影响选举,重新选举主标签页 // 但如果心跳已停止,则不执行选举,除非收到其他标签页消息 if (!this._isHeartbeatStopped) { if (!isCurrentlyHidden) { // 立即进行选举 this.electMaster(); } else { // 页面隐藏时,延时选举 setTimeout(this.electMaster.bind(this), 500); } } } }; /** * 处理页面卸载 */ this.handleBeforeUnload = () => { this.isClosing = true; this.sendCloseMessage(); }; /** * 处理接收到的消息 */ this.handleMessage = (event) => { const message = event.data; if (message.tabId === this.tabId) { // 忽略自己的消息 return; } if (this.isClosing) { // 已经在关闭过程中,忽略消息 return; } // 收到其他标签页的消息,处理心跳状态 // 这是恢复心跳的唯一途径 this.handleExternalMessage(); this.log("Received message", message); // 更新已知标签页列表(除非是关闭消息) if (message.type !== "tab-closed") { const tabInfo = this.knownTabsCache.get(message.tabId) || { lastSeen: message.timestamp, lastActive: message.lastActive || message.timestamp, }; tabInfo.lastSeen = message.timestamp; // 更新活动时间 if (message.lastActive) { tabInfo.lastActive = message.lastActive; } // 更新隐藏状态 if (message.type === "tab-hidden") { tabInfo.isHidden = true; } else if (message.type === "tab-visible") { tabInfo.isHidden = false; } else if (message.isHidden !== undefined) { tabInfo.isHidden = message.isHidden; } this.setTabInfo(message.tabId, tabInfo); } // 处理不同类型的消息 switch (message.type) { case "request-tabs-info": // 回应自己的信息 this.sendMessage("tab-info", { isHidden: this.isHidden, isMaster: this.isMaster, masterTabId: this.masterTabId, lastActive: this.lastActiveTime, heartbeatMode: this.heartbeatMode, }); break; case "tab-info": // 无需特殊处理,已在上面更新了标签页信息 break; case "tab-opened": // 触发标签页打开事件 this.emit("tabOpened", message.tabId, message); if (!this.options.allowMultipleTabs) { this.handleDuplicate(); } else { // 重新选举主标签页 this.electMaster(); } break; case "heartbeat": // 更新主标签页信息和标签页隐藏状态 if (message.masterTabId) { this.masterTabId = message.masterTabId; // 如果认为自己是主标签页,但收到了其他主标签页的心跳,重新选举 if (this.isMaster && message.masterTabId !== this.tabId) { this.electMaster(); } } // 更新标签页隐藏状态变化事件 if (message.isHidden !== undefined) { const tabInfo = this.knownTabsCache.get(message.tabId); if (tabInfo) { const wasHidden = tabInfo.isHidden; if (wasHidden !== message.isHidden) { // 触发标签页隐藏/可见事件 if (message.isHidden) { this.emit("tabHidden", message.tabId); } else { this.emit("tabVisible", message.tabId); } // 标签页可见性改变,重新选举 this.electMaster(); } } } break; case "tab-closed": // 从已知标签页中移除 this.deleteTabInfo(message.tabId); // 触发标签页关闭事件 this.emit("tabClosed", message.tabId); // 如果主标签页关闭,重新选举 if (this.masterTabId === message.tabId) { this.electMaster(); } // 检查是否还有其他标签页存在 if (this.knownTabsCache.size <= 1) { this.otherTabsDetected = false; this.log("All other tabs have closed, preparing to stop heartbeat"); // 设置停止心跳的计时器 if (this.heartbeatStopTimeout !== null) { clearTimeout(this.heartbeatStopTimeout); } this.heartbeatStopTimeout = window.setTimeout(this.stopHeartbeat.bind(this), this.options.heartbeatStopThresholdMs); } break; case "tab-hidden": // 触发标签页隐藏事件 this.emit("tabHidden", message.tabId); // 标签页可见性改变,重新选举 setTimeout(this.electMaster.bind(this), 500); break; case "tab-visible": // 触发标签页可见事件 this.emit("tabVisible", message.tabId); // 标签页可见性改变,重新选举 setTimeout(this.electMaster.bind(this), 500); break; case "become-master": // 其他标签页宣布成为主标签页 this.masterTabId = message.tabId; if (this.isMaster && message.tabId !== this.tabId) { // 检查另一个标签页的活动时间是否更新 const otherTabInfo = this.knownTabsCache.get(message.tabId); const otherTabActive = (otherTabInfo === null || otherTabInfo === void 0 ? void 0 : otherTabInfo.lastActive) || message.lastActive || 0; // 如果另一个标签页的活动时间更近,或者当前标签页隐藏而其他标签页可见,则让出主标签页地位 if (otherTabActive > this.lastActiveTime || (this.isHidden && otherTabInfo && !otherTabInfo.isHidden)) { this.log("Other tab is more recently active or more visible, yielding master status"); this.isMaster = false; // 触发从标签页事件 this.emit("slave"); } else { // 否则维持当前主标签页状态,并稍后重新选举以解决冲突 this.log("Maintaining master status, will re-elect later"); setTimeout(this.electMaster.bind(this), 600); } } break; case "state-sync-request": // 处理状态同步请求 this.handleStateSyncRequest(message.tabId); break; case "state-sync-response": // 接收状态同步响应 if (message.targetTabId === this.tabId && message.state) { this.log("Received application state"); this.emit("stateReceived", message.state); } break; case "custom": // 处理自定义消息 if (message.data) { this.emit("message", message.data, message.tabId); } break; } }; this.options = Object.assign(Object.assign({}, MasterTabCoordinator.DEFAULT_OPTIONS), options); this.channelName = this.options.channelName; this.tabId = this.generateTabId(); // 检查 BroadcastChannel 支持 if (typeof BroadcastChannel === "undefined") { console.warn("This browser doesn't support BroadcastChannel API. TabSyncCoordinator functionality will be limited"); } // 事件处理器集合 this.eventHandlers = { master: [], slave: [], tabOpened: [], tabClosed: [], tabHidden: [], tabVisible: [], message: [], duplicate: [], stateReceived: [], }; // 监听页面聚焦/点击/滚动事件,更新最后活动时间 this.setupActivityTracking(); } /** * 设置活动追踪 */ setupActivityTracking() { // 页面获取焦点时更新活动时间 window.addEventListener("focus", this.updateActivityTime); // 用户交互时更新活动时间 // document.addEventListener("click", this.updateActivityTime); // document.addEventListener("keydown", this.updateActivityTime); // document.addEventListener("touchstart", this.updateActivityTime); // document.addEventListener("scroll", this.updateActivityTime, { passive: true }); // 页面可见性变化时也更新活动时间 document.addEventListener("visibilitychange", this.updateActivityTime); } /** * 更新选项 */ updateOptions(options) { const previousOptions = Object.assign({}, this.options); this.options = Object.assign(Object.assign({}, this.options), options); // 如果通道名称已更改且通道已初始化,则需要重新初始化 if (options.channelName && options.channelName !== this.channelName && this.channel) { this.channelName = this.options.channelName; this.reinitialize(); } // 如果心跳相关配置已更改,更新心跳机制 if (options.heartbeatIntervalMs !== undefined && options.heartbeatIntervalMs !== previousOptions.heartbeatIntervalMs) { this.updateHeartbeatSchedule(); } } /** * 添加事件监听器 */ on(event, handler) { // @ts-ignore - 类型断言 this.eventHandlers[event].push(handler); return this; } /** * 移除事件监听器 */ off(event, handler) { // @ts-ignore - 类型断言 this.eventHandlers[event] = this.eventHandlers[event].filter((h) => h !== handler); return this; } /** * 触发事件 */ emit(event, ...args) { // @ts-ignore - 类型断言 this.eventHandlers[event].forEach((handler) => handler(...args)); } /** * 添加或更新标签页信息 */ setTabInfo(tabId, info) { this.knownTabsCache.set(tabId, info); } /** * 删除标签页信息 */ deleteTabInfo(tabId) { return this.knownTabsCache.delete(tabId); } /** * 更新心跳调度 * 根据当前状态设置心跳频率 */ updateHeartbeatSchedule() { // 清除现有的心跳定时器 if (this.heartbeatInterval !== null) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } if (this.heartbeatStopTimeout !== null) { clearTimeout(this.heartbeatStopTimeout); this.heartbeatStopTimeout = null; } // 如果已经在停止心跳模式,不重新设置计时器 if (this._isHeartbeatStopped) { this.log("Heartbeat already stopped, not setting new heartbeat timer"); return; } // 设置普通心跳间隔 this.heartbeatInterval = window.setInterval(this.sendHeartbeat.bind(this), this.options.heartbeatIntervalMs); // 设置心跳停止定时器,如果长时间没收到其他标签页消息,停止心跳 this.heartbeatStopTimeout = window.setTimeout(() => { const now = Date.now(); // 只有当没有检测到其他标签页或长时间没收到心跳时才停止 if (!this.otherTabsDetected || (this.lastHeartbeatReceived > 0 && now - this.lastHeartbeatReceived > this.options.heartbeatStopThresholdMs)) { this.stopHeartbeat(); } else { // 重置计时器 if (this.heartbeatStopTimeout !== null) { clearTimeout(this.heartbeatStopTimeout); } this.heartbeatStopTimeout = window.setTimeout(this.stopHeartbeat.bind(this), this.options.heartbeatStopThresholdMs); } }, this.options.heartbeatStopThresholdMs); } /** * 停止心跳发送 */ stopHeartbeat() { if (this._isHeartbeatStopped) return; this.log("No heartbeats received from other tabs for a long time, stopping heartbeat"); // 清除心跳定时器 if (this.heartbeatInterval !== null) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } this._isHeartbeatStopped = true; this.heartbeatMode = "stopped"; // 发送一条最后的心跳,告知其他可能存在的标签页当前状态 this.sendMessage("heartbeat", { masterTabId: this.isMaster ? this.tabId : this.masterTabId, isHidden: this.isHidden, lastActive: this.lastActiveTime, heartbeatMode: this.heartbeatMode, }); } /** * 恢复心跳发送 */ restoreHeartbeat() { if (!this._isHeartbeatStopped) return; this.log("Resuming heartbeat"); this._isHeartbeatStopped = false; this.heartbeatMode = "active"; this.otherTabsDetected = true; // 重新设置心跳计时器 this.updateHeartbeatSchedule(); } /** * 收到其他标签页消息时的处理 * 这是恢复心跳的唯一途径 */ handleExternalMessage() { const now = Date.now(); this.lastHeartbeatReceived = now; this.otherTabsDetected = true; // 如果当前处于心跳停止模式,恢复心跳 if (this._isHeartbeatStopped) { this.log("Detected activity from other tab, resuming heartbeat"); this.restoreHeartbeat(); // 立即发送一次心跳,确保当前状态及时通知其他标签页 this.firstHeartbeatAfterRestore = true; setTimeout(this.sendHeartbeat.bind(this), 500); } // 重置心跳停止计时器 if (this.heartbeatStopTimeout !== null) { clearTimeout(this.heartbeatStopTimeout); } this.heartbeatStopTimeout = window.setTimeout(this.stopHeartbeat.bind(this), this.options.heartbeatStopThresholdMs); } /** * 初始化 MasterTabCoordinator */ initialize() { if (this.channel) { this.log("Already initialized, skipping"); return this; } this.log("Initializing MasterTabCoordinator"); try { // 创建 BroadcastChannel if (typeof BroadcastChannel !== "undefined") { this.channel = new BroadcastChannel(this.channelName); this.channel.addEventListener("message", this.handleMessage); } else { console.warn("This browser doesn't support BroadcastChannel API, operating in limited functionality mode"); } // 设置初始状态 this.isHidden = document.hidden; this.lastActiveTime = Date.now(); this.lastHeartbeatReceived = 0; // 初始化为0,表示还未收到过其他标签页的心跳 // 添加当前标签页到已知标签页列表 this.setTabInfo(this.tabId, { lastSeen: Date.now(), isHidden: document.hidden, lastActive: this.lastActiveTime, }); // 发送初始消息 this.sendMessage("tab-opened", { url: window.location.href, isHidden: document.hidden, lastActive: this.lastActiveTime, }); // 请求其他标签页发送信息以便新标签页能获取完整的标签页列表 setTimeout(() => { this.sendMessage("request-tabs-info"); }, 100); // 设置心跳发送 - 初始使用正常频率,稍后会根据情况调整 this.updateHeartbeatSchedule(); // 立即发送心跳 setTimeout(this.sendHeartbeat.bind(this), 300); // 设置清理过期标签页定时器 this.cleanupTimeout = window.setTimeout(this.cleanupStaleTabs.bind(this), 2000); // 设置主标签页选举 this.masterElectionTimeout = window.setTimeout(() => { this.electMaster(); // 如果不是主标签页,请求状态同步 setTimeout(() => { if (!this.isMaster && this.masterTabId) { this.requestStateSync(); } this.isInitialized = true; // 在初始化完成后,检查是否有其他标签页 // 如果经过一定时间后仍未检测到其他标签页,准备停止心跳 setTimeout(() => { if (this.knownTabsCache.size <= 1) { this.log("No other tabs detected, preparing to stop heartbeat"); this.otherTabsDetected = false; // 设置停止心跳的计时器 if (this.heartbeatStopTimeout !== null) { clearTimeout(this.heartbeatStopTimeout); } this.heartbeatStopTimeout = window.setTimeout(this.stopHeartbeat.bind(this), this.options.heartbeatStopThresholdMs); } }, this.options.initialDetectionTimeMs); }, 500); }, 1000); // 添加事件监听器 document.addEventListener("visibilitychange", this.handleVisibilityChange); // 只监听unload事件,不监听beforeunload window.addEventListener("unload", this.handleBeforeUnload); return this; } catch (error) { console.error("Failed to initialize TabSyncCoordinator:", error); return this; } } /** * 重新初始化 (例如当通道名称改变时) */ reinitialize() { this.destroy(); this.initialize(); } /** * 销毁 TabManager */ destroy() { this.isClosing = true; // 移除活动追踪 window.removeEventListener("focus", this.updateActivityTime); window.removeEventListener("visibilitychange", this.updateActivityTime); // document.removeEventListener("click", this.updateActivityTime); // document.removeEventListener("keydown", this.updateActivityTime); // document.removeEventListener("touchstart", this.updateActivityTime); // document.removeEventListener("scroll", this.updateActivityTime); if (this.heartbeatInterval !== null) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } if (this.heartbeatStopTimeout !== null) { clearTimeout(this.heartbeatStopTimeout); this.heartbeatStopTimeout = null; } if (this.cleanupTimeout !== null) { clearTimeout(this.cleanupTimeout); this.cleanupTimeout = null; } if (this.masterElectionTimeout !== null) { clearTimeout(this.masterElectionTimeout); this.masterElectionTimeout = null; } // 发送关闭消息 this.sendCloseMessage(); // 移除事件监听器 document.removeEventListener("visibilitychange", this.handleVisibilityChange); // window.removeEventListener("beforeunload", this.handleBeforeUnload); window.removeEventListener("unload", this.handleBeforeUnload); // 关闭通道 if (this.channel) { this.channel.removeEventListener("message", this.handleMessage); this.channel.close(); this.channel = null; } // 清空标签页缓存 this.knownTabsCache.clear(); console.warn("MasterTabCoordinator destroyed"); } /** * 生成唯一的标签页 ID */ generateTabId() { return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; } /** * 日志记录 */ log(...args) { if (this.options.debug) { const prefix = `[TAG]: [${this.tabId}${this.isMaster ? "(Master)" : ""}]`; console.log(prefix, ...args); } } /** * 发送消息 */ sendMessage(type, additionalData = {}) { if (!this.channel || this.isClosing) return; const message = Object.assign({ type, tabId: this.tabId, timestamp: Date.now(), heartbeatMode: this.heartbeatMode }, additionalData); this.channel.postMessage(message); this.log("Sent message", type, additionalData); } /** * 发送心跳 */ sendHeartbeat() { if (this.isClosing) return; // 如果心跳已停止且不是恢复后的第一次心跳,跳过发送 if (this._isHeartbeatStopped && !this.firstHeartbeatAfterRestore) { return; } // 恢复后的第一次心跳发送完后,重置标志 if (this.firstHeartbeatAfterRestore) { this.firstHeartbeatAfterRestore = false; } this.sendMessage("heartbeat", { masterTabId: this.isMaster ? this.tabId : this.masterTabId, isHidden: this.isHidden, lastActive: this.lastActiveTime, heartbeatMode: this.heartbeatMode, }); // 更新当前标签页的 lastSeen 时间 const tabInfo = this.knownTabsCache.get(this.tabId) || { lastSeen: Date.now(), isHidden: this.isHidden, lastActive: this.lastActiveTime, }; tabInfo.lastSeen = Date.now(); this.setTabInfo(this.tabId, tabInfo); } /** * 发送关闭消息 */ sendCloseMessage() { this.sendMessage("tab-closed"); } /** * 清理过期标签页 */ cleanupStaleTabs() { if (this.isClosing) return; const now = Date.now(); let hasStaleTab = false; let otherTabsExist = false; for (const [tabId, tabInfo] of this.knownTabsCache.entries()) { if (tabId !== this.tabId) { otherTabsExist = true; } if (tabId !== this.tabId && now - tabInfo.lastSeen > this.options.tabTimeoutMs) { this.log(`Detected stale tab ${tabId}, presumed closed`); this.knownTabsCache.delete(tabId); hasStaleTab = true; // 触发标签页关闭事件 this.emit("tabClosed", tabId); } } // 更新是否检测到其他标签页 if (this.otherTabsDetected !== otherTabsExist) { this.otherTabsDetected = otherTabsExist; // 如果不再有其他标签页,准备停止心跳 if (!otherTabsExist && !this._isHeartbeatStopped) { this.log("No more other tabs detected, preparing to stop heartbeat"); // 设置停止心跳的计时器 if (this.heartbeatStopTimeout !== null) { clearTimeout(this.heartbeatStopTimeout); } this.heartbeatStopTimeout = window.setTimeout(this.stopHeartbeat.bind(this), this.options.heartbeatStopThresholdMs); } } // 如果删除了过期标签页,且当前是主标签页或主标签页已过期,重新选举 if (hasStaleTab && (this.isMaster || (this.masterTabId && !this.knownTabsCache.has(this.masterTabId)))) { this.electMaster(); } // 继续定期清理 this.cleanupTimeout = window.setTimeout(this.cleanupStaleTabs.bind(this), this.options.tabTimeoutMs / 3); } /** * 执行主标签页选举 */ electMaster() { if (this.isClosing) return; // 首先清理过期标签页 const now = Date.now(); for (const [tabId, tabInfo] of this.knownTabsCache.entries()) { if (tabId !== this.tabId && now - tabInfo.lastSeen > this.options.tabTimeoutMs) { this.log(`Removing stale tab ${tabId} during election`); this.knownTabsCache.delete(tabId); } } // 检查是否所有标签页都是可见的或都是隐藏的 let allVisible = true; let allHidden = true; for (const [_, tabInfo] of this.knownTabsCache.entries()) { if (tabInfo.isHidden) { allVisible = false; } else { allHidden = false; } } const allSameVisibility = allVisible || allHidden; // 候选主标签页ID let candidateTabId = this.tabId; let candidateIsHidden = this.isHidden; let candidateLastActive = this.lastActiveTime || 0; let candidateCreationTime = parseInt(candidateTabId.split("-")[0]); for (const [tabId, tabInfo] of this.knownTabsCache.entries()) { if (tabId === this.tabId) continue; // 自己已经是候选了 const tabIsHidden = tabInfo.isHidden === true; const tabLastActive = tabInfo.lastActive || 0; const tabCreationTime = parseInt(tabId.split("-")[0]); if (allSameVisibility) { // 如果所有标签页都是可见的或都是隐藏的,优先选择最近活动的标签页 if (tabLastActive > candidateLastActive) { candidateTabId = tabId; candidateIsHidden = tabIsHidden; candidateLastActive = tabLastActive; candidateCreationTime = tabCreationTime; } // 如果活动时间相同,选择最晚创建的标签页(时间戳最大) else if (tabLastActive === candidateLastActive && tabCreationTime > candidateCreationTime) { candidateTabId = tabId; candidateIsHidden = tabIsHidden; candidateLastActive = tabLastActive; candidateCreationTime = tabCreationTime; } } else { // 如果有些标签页可见,有些隐藏,优先选择可见的标签页 // 如果当前候选是隐藏的,但当前检查的是可见的,切换到可见标签页 if (candidateIsHidden && !tabIsHidden) { candidateTabId = tabId; candidateIsHidden = tabIsHidden; candidateLastActive = tabLastActive; candidateCreationTime = tabCreationTime; } // 如果两者可见性相同 else if (candidateIsHidden === tabIsHidden) { // 在可见性相同的情况下,优先选择最近活动的标签页 if (tabLastActive > candidateLastActive) { candidateTabId = tabId; candidateIsHidden = tabIsHidden; candidateLastActive = tabLastActive; candidateCreationTime = tabCreationTime; } // 如果活动时间也相同,选择最晚创建的标签页(时间戳最大) else if (tabLastActive === candidateLastActive && tabCreationTime > candidateCreationTime) { candidateTabId = tabId; candidateIsHidden = tabIsHidden; candidateLastActive = tabLastActive; candidateCreationTime = tabCreationTime; } } } } this.log("Master tab election result", { allVisible, allHidden, allSameVisibility, candidateTabId, candidateLastActive, isCurrent: candidateTabId === this.tabId, }); // 如果当前标签页是选中的主标签页,设置为主标签页 if (candidateTabId === this.tabId) { if (!this.isMaster) { this.log("Becoming master tab"); this.isMaster = true; this.masterTabId = this.tabId; // 如果心跳未停止,发送成为主标签页的消息 // 注意:如果心跳已停止,不会发送消息,除非收到其他标签页消息 if (!this._isHeartbeatStopped) { this.sendMessage("become-master", { lastActive: this.lastActiveTime, }); } // 触发主标签页事件 this.emit("master"); } } else { if (this.isMaster) { this.log("Becoming slave tab, new master is", candidateTabId); this.isMaster = false; // 触发从标签页事件 this.emit("slave"); } this.masterTabId = candidateTabId; } } /** * 处理重复标签页 */ handleDuplicate() { if (this.options.allowMultipleTabs) { this.log("Multiple tabs detected, but multi-tab mode is enabled"); return; } this.log("Duplicate tab detected, preparing to close"); this.isClosing = true; // 触发重复标签页事件 this.emit("duplicate"); // 发送关闭消息并重定向 this.sendCloseMessage(); setTimeout(() => { window.location.href = this.options.redirectUrl; }, this.options.redirectDelay); } /** * 处理状态同步请求 */ handleStateSyncRequest(requestingTabId) { if (this.isMaster && this.options.state) { this.sendMessage("state-sync-response", { state: this.options.state, targetTabId: requestingTabId, }); } } /** * 请求状态同步 */ requestStateSync() { if (!this.isMaster && this.masterTabId) { // 即使心跳已停止,也发送此重要消息 this.sendMessage("state-sync-request"); } } /** * 发送自定义消息到所有标签页 * 即使心跳已停止,也会发送此消息 */ broadcast(data) { this.sendMessage("custom", { data }); } /** * 获取当前是否为主标签页 */ isMasterTab() { return this.isMaster; } /** * 获取当前标签页是否隐藏 */ isTabHidden() { return this.isHidden; } /** * 获取当前标签页 ID */ getTabId() { return this.tabId; } /** * 获取主标签页 ID */ getMasterTabId() { return this.masterTabId; } /** * 获取已知标签页数量 */ getTabCount() { return this.knownTabsCache.size; } /** * 获取所有已知标签页 */ getKnownTabs() { // 返回已知标签页的副本 return new Map(this.knownTabsCache); } /** * 获取当前心跳模式 */ getHeartbeatMode() { return this.heartbeatMode; } /** * 手动更新活动时间 * 可在用户重要交互时调用 */ updateLastActiveTime() { this.updateActivityTime(); } /** * 获取心跳是否已停止 */ isHeartbeatStopped() { return this._isHeartbeatStopped; } /** * 设置要同步的应用状态 */ setState(state) { this.options.state = state; // 如果是主标签页,将状态广播给从标签页 if (this.isMaster) { // 即使心跳已停止,也发送此重要消息 this.sendMessage("state-broadcast", { state }); } } /** * 获取当前应用状态 */ getState() { return this.options.state; } } exports.MasterTabCoordinator = MasterTabCoordinator; MasterTabCoordinator.instance = null; // 默认选项 MasterTabCoordinator.DEFAULT_OPTIONS = { channelName: "tab-manager", debug: false, allowMultipleTabs: true, heartbeatIntervalMs: 5000, heartbeatStopThresholdMs: 60000, // 多久没收到心跳后停止发送 initialDetectionTimeMs: 5000, // 初始检测其他标签页的时间 tabTimeoutMs: 10000, redirectUrl: "about:blank", redirectDelay: 100, }; exports.default = MasterTabCoordinator; //# sourceMappingURL=MasterTabCoordinator.js.map