@brewer/dj-common 1.0.0-beta.13 → 1.0.0-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";const e={debug:10,info:20,warn:30,error:40,silent:50};class t{constructor(e,t="warn"){this.name=e,this.level=t}setLevel(e){this.level=e}getLevel(){return this.level}debug(...e){this.logAtLevel("debug",console.debug,e)}info(...e){this.logAtLevel("info",console.info,e)}warn(...e){this.logAtLevel("warn",console.warn,e)}error(...e){this.logAtLevel("error",console.error,e)}shouldLog(t){return"silent"!==t&&e[t]>=e[this.level]}logAtLevel(e,t,r){this.shouldLog(e)&&t(`[${this.name}]`,...r)}}class r{constructor(e={}){this.socket=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.callbackList=[],this.currentUrl=null,this.reconnectAttempts=0,this.manualClose=!1,this.config={...r.DEFAULT_CONFIG,...e},this.logger=new t("WebSocketClient",this.config.logLevel)}updateConfig(e){this.config={...this.config,...e},this.logger.setLevel(this.config.logLevel??r.DEFAULT_CONFIG.logLevel)}connect(e){const t=e||this.config.url;if(t){this.currentUrl=t,this.manualClose=!1;try{this.socket=new WebSocket(t),this.socket.onopen=()=>{this.logger.info("[WebSocketClient] 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),this.onOpen()},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{this.logger.info("[WebSocketClient] 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.onClose(e),!this.manualClose&&this.config.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{this.logger.error("[WebSocketClient] 连接错误",e),this.stopHeartbeat(),this.onError(e)}}catch(e){this.logger.error("[WebSocketClient] 连接失败",e),this.config.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else this.logger.error("[WebSocketClient] 缺少 WebSocket URL")}scheduleReconnect(){if(this.reconnectAttempts>=this.config.maxReconnectAttempts||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=this.config.maxReconnectAttempts&&this.logger.warn("[WebSocketClient] 已达到最大重连次数"));this.reconnectAttempts+=1;const e=Math.min(this.config.reconnectDelay*this.reconnectAttempts,this.config.reconnectDelayMax);this.logger.debug(`[WebSocketClient] 将在 ${e}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=window.setTimeout(()=>{this.connect(this.currentUrl)},e)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.currentUrl=null,this.reconnectAttempts=0}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void this.logger.warn("[WebSocketClient] WebSocket 未连接,无法发送消息");const t="string"==typeof e?e:JSON.stringify(e);this.socket.send(t)}handleIncoming(e){if(!e)return;let t;try{t=JSON.parse(e)}catch{return void this.logger.warn("[WebSocketClient] 无法解析消息",e)}if(!t?.type)return;this.callbackList.filter(e=>e.type===t.type).forEach(({callback:e})=>{try{e(t.data,t)}catch(e){this.logger.error("[WebSocketClient] 回调执行失败",e)}}),this.onMessage(t)}startHeartbeat(){this.stopHeartbeat(),this.socket&&this.socket.readyState===WebSocket.OPEN&&(this.heartbeatTimer=window.setInterval(()=>{if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return;const e=this.config.heartbeatMessage();this.send(e)},this.config.heartbeatInterval))}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}on(e,t){const r="string"==typeof e?{type:e,callback:t}:e;r.type&&"function"==typeof r.callback?this.callbackList.push(r):this.logger.warn("[WebSocketClient] 无效的回调配置",r)}off(e,t){this.callbackList=t?this.callbackList.filter(r=>!(r.type===e&&r.callback===t)):this.callbackList.filter(t=>t.type!==e)}clearCallbacks(){this.callbackList=[]}getReadyState(){return this.socket?.readyState??WebSocket.CLOSED}isConnected(){return this.socket?.readyState===WebSocket.OPEN}onOpen(){this.logger.info("[WebSocketClient] 连接打开")}onClose(e){this.logger.info("[WebSocketClient] 连接打开")}onError(e){this.logger.error("[WebSocketClient] 连接错误",e)}onMessage(e){this.logger.debug("[WebSocketClient] 收到消息",e)}}r.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,heartbeatMessage:()=>({type:"PING",timestamp:Date.now()}),logLevel:"warn"};class n{constructor(e){this.worker=null,this.port=null,this.callbacks=new Map,this.callbackIdCounter=0,this.connected=!1,this.visibilityListenerInitialized=!1,this.workerBlobUrl=null,this.onConnectedCallback=null,this.onDisconnectedCallback=null,this.onErrorCallback=null,this.onAuthConflictCallback=null,this.handleVisibilityChange=()=>{const e=!document.hidden;if(this.logger.debug(`[SharedWorkerManager] 页面可见性变化: ${e}`),this.port){const t={isVisible:e};this.sendToWorker("TAB_VISIBILITY",t)}},this.config=e,this.tabId=this.generateTabId(),this.logger=new t("SharedWorkerManager",e.logLevel??"warn")}generateTabId(){return`tab_${Date.now()}_${Math.random().toString(36).substring(2,9)}`}async start(){try{this.logger.debug("[SharedWorkerManager] 开始启动 SharedWorker");const e=this.getWorkerScriptBlob();this.workerBlobUrl=URL.createObjectURL(e),this.logger.debug(`[SharedWorkerManager] Worker Blob URL 创建成功: ${this.workerBlobUrl}`),this.logger.debug("[SharedWorkerManager] 正在创建 SharedWorker 实例..."),this.worker=new SharedWorker(this.workerBlobUrl,{name:"dj-common-websocket-worker"}),this.logger.debug("[SharedWorkerManager] ✅ SharedWorker 实例创建成功"),this.port=this.worker.port,this.port.onmessage=this.handleWorkerMessage.bind(this),this.port.start(),this.logger.debug("[SharedWorkerManager] ✅ MessagePort 已启动"),this.setupVisibilityListener();const t={heartbeatInterval:this.config.config.heartbeatInterval,maxReconnectAttempts:this.config.config.maxReconnectAttempts,reconnectDelay:this.config.config.reconnectDelay,reconnectDelayMax:this.config.config.reconnectDelayMax,autoReconnect:this.config.config.autoReconnect,logLevel:this.config.config.logLevel};return this.sendToWorker("TAB_INIT",{url:this.config.url,userId:this.config.userId,token:this.config.token,isVisible:!document.hidden,config:t,sharedWorkerIdleTimeout:this.config.sharedWorkerIdleTimeout}),this.logger.info("[SharedWorkerManager] SharedWorker 已启动"),!0}catch(e){return this.logger.error("[SharedWorkerManager] 启动 SharedWorker 失败",e),!1}}stop(){this.logger.debug("[SharedWorkerManager] 停止 SharedWorker"),this.port&&this.sendToWorker("TAB_DISCONNECT",{}),this.removeVisibilityListener(),this.port&&(this.port.close(),this.port=null),this.workerBlobUrl&&(URL.revokeObjectURL(this.workerBlobUrl),this.workerBlobUrl=null),this.worker=null,this.connected=!1,this.callbacks.clear()}send(e){if(!this.port)return void this.logger.warn("[SharedWorkerManager] MessagePort 未初始化,无法发送消息");const t={data:e};this.sendToWorker("TAB_SEND",t)}registerCallback(e){const t="callback_"+this.callbackIdCounter++,r={...e,id:t};if(this.callbacks.set(t,r),this.port){const r={type:e.type,callbackId:t};this.sendToWorker("TAB_REGISTER_CALLBACK",r),this.logger.debug(`[SharedWorkerManager] ✅ 已发送注册消息到 Worker: ${e.type} (${t})`)}else this.logger.warn(`[SharedWorkerManager] ⚠️ port 未初始化,无法发送注册消息: ${e.type}`);return this.logger.debug(`[SharedWorkerManager] 注册回调: ${e.type} (${t}), 当前回调总数: ${this.callbacks.size}`),t}unregisterCallback(e,t){if(t){let r=null;for(const[n,o]of this.callbacks.entries())if(o.type===e&&o.callback===t){r=n,this.callbacks.delete(n);break}if(r&&this.port){const t={type:e,callbackId:r};this.sendToWorker("TAB_UNREGISTER_CALLBACK",t)}this.logger.debug(`[SharedWorkerManager] 取消注册回调: ${e} (${r})`)}else{const t=[];for(const[r,n]of this.callbacks.entries())n.type===e&&(t.push(r),this.callbacks.delete(r));if(this.port){const t={type:e};this.sendToWorker("TAB_UNREGISTER_CALLBACK",t)}this.logger.debug(`[SharedWorkerManager] 取消注册所有 ${e} 类型回调 (${t.length} 个)`)}}clearCallbacks(){for(const e of this.callbacks.values())if(this.port){const t={type:e.type,callbackId:e.id};this.sendToWorker("TAB_UNREGISTER_CALLBACK",t)}this.callbacks.clear(),this.logger.debug("[SharedWorkerManager] 已清空所有回调")}isConnected(){return this.connected}onConnected(e){this.onConnectedCallback=e}onDisconnected(e){this.onDisconnectedCallback=e}onError(e){this.onErrorCallback=e}onAuthConflict(e){this.onAuthConflictCallback=e}handleWorkerMessage(e){const t=e.data;switch(this.logger.debug(`[SharedWorkerManager] 📬 收到 Worker 消息, type: ${t.type}`),t.type){case"WORKER_CONNECTED":this.connected=!0,this.logger.info("[SharedWorkerManager] ✅ WebSocket 已连接"),this.onConnectedCallback?.();break;case"WORKER_DISCONNECTED":this.connected=!1,this.logger.info("[SharedWorkerManager] WebSocket 已断开"),this.onDisconnectedCallback?.();break;case"WORKER_MESSAGE":this.handleServerMessage(t.payload);break;case"WORKER_ERROR":this.logger.error("[SharedWorkerManager] Worker 错误",t.payload),this.onErrorCallback?.(t.payload);break;case"WORKER_AUTH_CONFLICT":this.logger.warn("[SharedWorkerManager] 身份冲突",t.payload),this.onAuthConflictCallback?.(t.payload);break;case"WORKER_PONG":this.logger.debug("[SharedWorkerManager] 收到 PONG");break;default:this.logger.warn("[SharedWorkerManager] 未知消息类型",t.type)}}handleServerMessage(e){const{message:t}=e;this.logger.debug(`[SharedWorkerManager] 📨 收到服务器消息, type: ${t.type}`),this.logger.debug("[SharedWorkerManager] 当前注册的回调类型:",Array.from(this.callbacks.values()).map(e=>e.type));let r=0;for(const e of this.callbacks.values())if(e.type===t.type){r++,this.logger.debug(`[SharedWorkerManager] ✅ 匹配到回调 ${e.type} (${e.id}),准备执行`);try{e.callback(t.data,t),this.logger.debug(`[SharedWorkerManager] ✅ 回调执行成功 ${e.type} (${e.id})`)}catch(e){this.logger.error("[SharedWorkerManager] ❌ 回调执行失败",e)}}0===r&&this.logger.warn(`[SharedWorkerManager] ⚠️ 没有匹配的回调: ${t.type}`)}sendToWorker(e,t){if(!this.port)return void this.logger.warn("[SharedWorkerManager] MessagePort 未初始化,无法发送消息");const r={type:e,payload:t,tabId:this.tabId,timestamp:Date.now()};try{this.port.postMessage(r)}catch(e){this.logger.error("[SharedWorkerManager] 发送消息到 Worker 失败",e)}}setupVisibilityListener(){"undefined"==typeof document||this.visibilityListenerInitialized||(document.addEventListener("visibilitychange",this.handleVisibilityChange),this.visibilityListenerInitialized=!0,this.logger.debug("[SharedWorkerManager] 已设置页面可见性监听"))}removeVisibilityListener(){"undefined"!=typeof document&&this.visibilityListenerInitialized&&(document.removeEventListener("visibilitychange",this.handleVisibilityChange),this.visibilityListenerInitialized=!1,this.logger.debug("[SharedWorkerManager] 已移除页面可见性监听"))}getWorkerScriptBlob(){try{const e=this.getWorkerScriptContent();this.logger.debug(`[SharedWorkerManager] 正在创建 Worker Blob, 代码长度: ${e.length}`);const t=new Blob([e],{type:"application/javascript"});return this.logger.debug(`[SharedWorkerManager] ✅ Worker Blob 创建成功, size: ${t.size}`),t}catch(e){throw this.logger.error("[SharedWorkerManager] ❌ 创建 Worker Blob 失败:",e),e}}getWorkerScriptContent(){const e="const WorkerToTabMessageType = {\n WORKER_READY: 'WORKER_READY',\n WORKER_MESSAGE: 'WORKER_MESSAGE',\n WORKER_CONNECTED: 'WORKER_CONNECTED',\n WORKER_DISCONNECTED: 'WORKER_DISCONNECTED',\n WORKER_ERROR: 'WORKER_ERROR',\n WORKER_AUTH_CONFLICT: 'WORKER_AUTH_CONFLICT',\n WORKER_PONG: 'WORKER_PONG',\n};\nclass WebSocketManager {\n constructor() {\n this.tabs = new Map();\n this.socket = null;\n this.idleTimer = null;\n this.heartbeatTimer = null;\n this.reconnectTimer = null;\n this.reconnectAttempts = 0;\n this.manualClose = false;\n this.currentUrl = null;\n this.currentUserId = null;\n this.config = null;\n this.sharedWorkerIdleTimeout = 30000;\n }\n addTab(port, message) {\n const { tabId, payload } = message;\n const initPayload = payload;\n if (this.currentUserId && this.currentUserId !== initPayload.userId) {\n const conflictPayload = {\n currentUserId: this.currentUserId,\n newUserId: initPayload.userId,\n message: `检测到不同用户身份:当前连接用户为 ${this.currentUserId},新标签页尝试使用用户 ${initPayload.userId} 连接。将复用现有连接。`,\n };\n this.sendToTab(port, WorkerToTabMessageType.WORKER_AUTH_CONFLICT, conflictPayload);\n console.warn('[SharedWorker]', conflictPayload.message);\n }\n const tabInfo = {\n port,\n tabId,\n isVisible: initPayload.isVisible,\n registeredTypes: new Set(),\n callbackMap: new Map(),\n };\n this.tabs.set(tabId, tabInfo);\n console.log(`[SharedWorker] 标签页已添加: ${tabId}, 当前标签页数量: ${this.tabs.size}`);\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n this.currentUrl = `${initPayload.url}/${initPayload.userId}?token=${encodeURIComponent(initPayload.token)}`;\n this.currentUserId = initPayload.userId;\n this.config = initPayload.config;\n this.sharedWorkerIdleTimeout = initPayload.sharedWorkerIdleTimeout ?? 30000;\n this.connect();\n }\n else {\n this.sendToTab(port, WorkerToTabMessageType.WORKER_CONNECTED, {});\n }\n this.resetIdleTimer();\n }\n removeTab(tabId) {\n this.tabs.delete(tabId);\n console.log(`[SharedWorker] 标签页已移除: ${tabId}, 剩余标签页数量: ${this.tabs.size}`);\n if (this.tabs.size === 0) {\n console.log(`[SharedWorker] 所有标签页已关闭,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`);\n this.startIdleTimer();\n }\n else {\n this.checkAllTabsVisibility();\n }\n }\n updateTabVisibility(tabId, isVisible) {\n const tab = this.tabs.get(tabId);\n if (!tab) {\n console.warn(`[SharedWorker] 标签页不存在: ${tabId}`);\n return;\n }\n tab.isVisible = isVisible;\n console.log(`[SharedWorker] 标签页 ${tabId} 可见性更新: ${isVisible}`);\n this.checkAllTabsVisibility();\n }\n checkAllTabsVisibility() {\n if (this.tabs.size === 0)\n return;\n const allHidden = Array.from(this.tabs.values()).every((tab) => !tab.isVisible);\n if (allHidden) {\n console.log(`[SharedWorker] 所有标签页都不可见,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`);\n this.startIdleTimer();\n }\n else {\n console.log('[SharedWorker] 至少有一个标签页可见,保持连接');\n this.resetIdleTimer();\n }\n }\n startIdleTimer() {\n this.clearIdleTimer();\n this.idleTimer = globalThis.setTimeout(() => {\n console.log('[SharedWorker] 空闲超时,断开连接');\n this.disconnect();\n }, this.sharedWorkerIdleTimeout);\n }\n resetIdleTimer() {\n this.clearIdleTimer();\n }\n clearIdleTimer() {\n if (this.idleTimer !== null) {\n clearTimeout(this.idleTimer);\n this.idleTimer = null;\n }\n }\n connect() {\n if (!this.currentUrl) {\n console.error('[SharedWorker] 缺少 WebSocket URL');\n return;\n }\n this.manualClose = false;\n try {\n this.socket = new WebSocket(this.currentUrl);\n this.socket.onopen = () => {\n console.log('[SharedWorker] ✅ WebSocket 连接成功');\n this.reconnectAttempts = 0;\n this.startHeartbeat();\n console.log(`[SharedWorker] 通知 ${this.tabs.size} 个标签页: 已连接`);\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_CONNECTED, {});\n };\n this.socket.onmessage = (event) => {\n this.handleIncoming(event.data);\n };\n this.socket.onclose = (event) => {\n console.log('[SharedWorker] WebSocket 连接关闭', event.code, event.reason);\n this.stopHeartbeat();\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_DISCONNECTED, {});\n if (!this.manualClose && this.config?.autoReconnect) {\n this.scheduleReconnect();\n }\n };\n this.socket.onerror = (event) => {\n console.error('[SharedWorker] WebSocket 连接错误', event);\n this.stopHeartbeat();\n const errorPayload = {\n message: 'WebSocket 连接错误',\n error: event,\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload);\n };\n }\n catch (error) {\n console.error('[SharedWorker] 创建 WebSocket 连接失败', error);\n const errorPayload = {\n message: '创建 WebSocket 连接失败',\n error,\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload);\n if (this.config?.autoReconnect && !this.manualClose) {\n this.scheduleReconnect();\n }\n }\n }\n scheduleReconnect() {\n const maxAttempts = this.config?.maxReconnectAttempts ?? 10;\n const reconnectDelay = this.config?.reconnectDelay ?? 3000;\n const reconnectDelayMax = this.config?.reconnectDelayMax ?? 10000;\n if (this.reconnectAttempts >= maxAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= maxAttempts) {\n console.warn('[SharedWorker] 已达到最大重连次数');\n }\n return;\n }\n this.reconnectAttempts += 1;\n const delay = Math.min(reconnectDelay * this.reconnectAttempts, reconnectDelayMax);\n console.log(`[SharedWorker] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`);\n this.reconnectTimer = globalThis.setTimeout(() => {\n this.connect();\n }, delay);\n }\n disconnect() {\n this.manualClose = true;\n this.stopHeartbeat();\n this.clearIdleTimer();\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.socket) {\n this.socket.close();\n this.socket = null;\n }\n this.reconnectAttempts = 0;\n }\n startHeartbeat() {\n this.stopHeartbeat();\n const heartbeatInterval = this.config?.heartbeatInterval ?? 25000;\n this.heartbeatTimer = globalThis.setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return;\n }\n const heartbeatData = { type: 'PING', timestamp: Date.now() };\n this.send(heartbeatData);\n }, heartbeatInterval);\n }\n stopHeartbeat() {\n if (this.heartbeatTimer !== null) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n send(data) {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n console.warn('[SharedWorker] WebSocket 未连接,无法发送消息');\n return;\n }\n const message = typeof data === 'string' ? data : JSON.stringify(data);\n this.socket.send(message);\n }\n handleIncoming(data) {\n if (!data)\n return;\n let message;\n try {\n message = JSON.parse(data);\n }\n catch {\n console.warn('[SharedWorker] 无法解析消息', data);\n return;\n }\n if (!message?.type) {\n return;\n }\n console.log(`[SharedWorker] 📨 收到服务器消息, type: ${message.type}`, message);\n const serverMessagePayload = {\n data,\n message,\n };\n let sentCount = 0;\n for (const tab of this.tabs.values()) {\n console.log(`[SharedWorker] 检查标签页 ${tab.tabId}, 注册的类型:`, Array.from(tab.registeredTypes));\n if (tab.registeredTypes.has(message.type)) {\n console.log(`[SharedWorker] ✅ 发送消息到标签页 ${tab.tabId}, type: ${message.type}`);\n this.sendToTab(tab.port, WorkerToTabMessageType.WORKER_MESSAGE, serverMessagePayload);\n sentCount++;\n }\n }\n console.log(`[SharedWorker] 消息分发完成, type: ${message.type}, 发送给 ${sentCount} 个标签页`);\n }\n registerCallback(tabId, payload) {\n const tab = this.tabs.get(tabId);\n if (!tab) {\n console.warn(`[SharedWorker] ⚠️ 标签页不存在: ${tabId}`);\n return;\n }\n tab.registeredTypes.add(payload.type);\n tab.callbackMap.set(payload.callbackId, payload.type);\n console.log(`[SharedWorker] ✅ 标签页 ${tabId} 注册回调: ${payload.type} (${payload.callbackId})`);\n console.log(`[SharedWorker] 标签页 ${tabId} 当前注册的所有类型:`, Array.from(tab.registeredTypes));\n }\n unregisterCallback(tabId, payload) {\n const tab = this.tabs.get(tabId);\n if (!tab) {\n console.warn(`[SharedWorker] 标签页不存在: ${tabId}`);\n return;\n }\n if (payload.callbackId) {\n const type = tab.callbackMap.get(payload.callbackId);\n if (type) {\n tab.callbackMap.delete(payload.callbackId);\n const hasOtherCallbacks = Array.from(tab.callbackMap.values()).some((t) => t === type);\n if (!hasOtherCallbacks) {\n tab.registeredTypes.delete(type);\n }\n console.log(`[SharedWorker] 标签页 ${tabId} 取消注册回调: ${type}`);\n }\n }\n else {\n tab.registeredTypes.delete(payload.type);\n for (const [callbackId, type] of tab.callbackMap.entries()) {\n if (type === payload.type) {\n tab.callbackMap.delete(callbackId);\n }\n }\n console.log(`[SharedWorker] 标签页 ${tabId} 取消注册所有 ${payload.type} 类型回调`);\n }\n }\n sendToTab(port, type, payload) {\n const message = {\n type,\n payload,\n timestamp: Date.now(),\n };\n try {\n port.postMessage(message);\n }\n catch (error) {\n console.error('[SharedWorker] 发送消息到标签页失败', error);\n }\n }\n broadcastToAllTabs(type, payload) {\n for (const tab of this.tabs.values()) {\n this.sendToTab(tab.port, type, payload);\n }\n }\n}\nconst wsManager = new WebSocketManager();\nglobalThis.onconnect = (event) => {\n const port = event.ports[0];\n port.onmessage = (e) => {\n const message = e.data;\n console.log(`[SharedWorker] 📬 收到标签页消息, type: ${message.type}, tabId: ${message.tabId}`);\n switch (message.type) {\n case 'TAB_INIT':\n wsManager.addTab(port, message);\n break;\n case 'TAB_SEND':\n wsManager.send(message.payload.data);\n break;\n case 'TAB_VISIBILITY':\n wsManager.updateTabVisibility(message.tabId, message.payload.isVisible);\n break;\n case 'TAB_REGISTER_CALLBACK':\n console.log(`[SharedWorker] 🔔 处理注册回调请求:`, message.payload);\n wsManager.registerCallback(message.tabId, message.payload);\n break;\n case 'TAB_UNREGISTER_CALLBACK':\n wsManager.unregisterCallback(message.tabId, message.payload);\n break;\n case 'TAB_DISCONNECT':\n wsManager.removeTab(message.tabId);\n break;\n case 'TAB_PING':\n port.postMessage({\n type: 'WORKER_PONG',\n timestamp: Date.now(),\n });\n break;\n default:\n console.warn('[SharedWorker] 未知消息类型', message.type);\n }\n };\n port.start();\n};\n";return this.logger.debug("[SharedWorkerManager] Worker 脚本长度: 12612 字符"),e}}class o{static updateLoggerLevel(){o.logger.setLevel(o.config.logLevel??o.DEFAULT_CONFIG.logLevel)}static initVisibilityListener(){"undefined"==typeof document||o.visibilityListenerInitialized||(document.addEventListener("visibilitychange",o.handleVisibilityChange),o.visibilityListenerInitialized=!0,o.logger.debug("[MessageSocket] 已初始化页面可见性监听"))}static removeVisibilityListener(){"undefined"!=typeof document&&o.visibilityListenerInitialized&&(document.removeEventListener("visibilitychange",o.handleVisibilityChange),o.visibilityListenerInitialized=!1,o.logger.debug("[MessageSocket] 已移除页面可见性监听"))}static detectSharedWorkerSupport(){return"undefined"!=typeof SharedWorker&&"undefined"!=typeof Blob}static detectVisibilitySupport(){return"undefined"!=typeof document&&void 0!==document.hidden}static determineConnectionMode(){const e=o.config.connectionMode||"auto";return"auto"===e?this.detectSharedWorkerSupport()?(o.logger.info("[MessageSocket] 自动选择 SharedWorker 模式"),"sharedWorker"):this.detectVisibilitySupport()&&o.config.enableVisibilityManagement?(o.logger.info("[MessageSocket] 自动选择 Visibility 模式"),"visibility"):(o.logger.info("[MessageSocket] 自动选择 Normal 模式"),"normal"):"sharedWorker"===e?this.detectSharedWorkerSupport()?(o.logger.info("[MessageSocket] 使用 SharedWorker 模式"),"sharedWorker"):(o.logger.warn("[MessageSocket] 浏览器不支持 SharedWorker,降级到 Visibility 模式"),this.detectVisibilitySupport()&&o.config.enableVisibilityManagement?"visibility":(o.logger.warn("[MessageSocket] 降级到 Normal 模式"),"normal")):"visibility"===e?this.detectVisibilitySupport()?(o.logger.info("[MessageSocket] 使用 Visibility 模式"),"visibility"):(o.logger.warn("[MessageSocket] 浏览器不支持 Visibility API,降级到 Normal 模式"),"normal"):(o.logger.info("[MessageSocket] 使用 Normal 模式"),"normal")}static configure(e){o.config={...o.config,...e}}static setConfig(e){return o.config={...o.config,...e},o.updateLoggerLevel(),o.config.callbacks&&o.config.callbacks.length>0&&o.setCallbacks(o.config.callbacks),o}static setCallbacks(e){return e&&0!==e.length?(o.config.callbacks=e,o):(this.logger.warn("[MessageSocket] 回调列表为空,无法设置回调"),o)}static start(e){if(!o.config.url)return void this.logger.error("[MessageSocket] 缺少配置 url, 请先调用 setConfig 设置配置!");const{userId:t,token:r}=e;t&&r?(this.logger.info("[MessageSocket] 开始连接",t),o.currentMode=o.determineConnectionMode(),"sharedWorker"===o.currentMode?o.startWithSharedWorker(e):"visibility"===o.currentMode?o.startWithVisibility(e):o.startWithNormalMode(e)):this.logger.error("[MessageSocket] 缺少 userId 或 token,无法启动")}static async startWithSharedWorker(e){const{userId:t,token:r}=e;o.stop(),o.currentUserId=t,o.currentToken=r,o.workerManager=new n({url:o.config.url,userId:t,token:r,isVisible:"undefined"==typeof document||!document.hidden,config:o.config,sharedWorkerIdleTimeout:o.config.sharedWorkerIdleTimeout,logLevel:o.config.logLevel}),o.workerManager.onError(t=>{o.logger.warn("[MessageSocket] SharedWorker 失败,降级到 Visibility 模式",t),o.currentMode="visibility",o.workerManager=null,o.startWithVisibility(e)});if(!await o.workerManager.start())return o.logger.warn("[MessageSocket] SharedWorker 启动失败,降级到 Visibility 模式"),o.currentMode="visibility",o.workerManager=null,void o.startWithVisibility(e);o.config.callbacks&&o.config.callbacks.length>0&&o.config.callbacks.forEach(e=>{o.workerManager.registerCallback(e)})}static startWithVisibility(e){const{userId:t,token:n}=e;if(o.client&&o.client.isConnected()&&o.currentUserId===t&&o.currentToken===n)return void this.logger.debug("[MessageSocket] 复用现有连接");o.stop(),o.currentUserId=t,o.currentToken=n;const{url:s,...a}=o.config,i=`${s}/${t}?token=${encodeURIComponent(n)}`;o.client=new r({...a,url:i}),o.config.callbacks&&o.config.callbacks.length>0&&o.config.callbacks.forEach(e=>o.client.on(e)),o.initVisibilityListener(),o.client.connect()}static startWithNormalMode(e){const{userId:t,token:n}=e;if(o.client&&o.client.isConnected()&&o.currentUserId===t&&o.currentToken===n)return void this.logger.debug("[MessageSocket] 复用现有连接");o.stop(),o.currentUserId=t,o.currentToken=n;const{url:s,...a}=o.config,i=`${s}/${t}?token=${encodeURIComponent(n)}`;o.client=new r({...a,url:i}),o.config.callbacks&&o.config.callbacks.length>0&&o.config.callbacks.forEach(e=>o.client.on(e)),o.client.connect()}static stop(){o.client&&(o.client.disconnect(),o.client.clearCallbacks(),o.client=null),o.workerManager&&(o.workerManager.stop(),o.workerManager=null),o.removeVisibilityListener(),o.currentUserId=null,o.currentToken=null}static registerCallbacks(e){e?"object"==typeof e?"function"==typeof e.callback?"string"==typeof e.type?"sharedWorker"===o.currentMode&&o.workerManager?o.workerManager.registerCallback(e):o.client?o.client.on(e):this.logger.warn("[MessageSocket] 无可用连接,无法注册回调"):this.logger.warn("[MessageSocket] 注册回调失败,type 不是字符串",e):this.logger.warn("[MessageSocket] 注册回调失败,callback 不是函数",e):this.logger.warn("[MessageSocket] 注册回调失败,entry 不是对象",e):this.logger.warn("[MessageSocket] 注册回调失败,缺少 entry",e)}static unregisterCallbacks(e,t){"sharedWorker"===o.currentMode&&o.workerManager?o.workerManager.unregisterCallback(e,t):o.client&&o.client.off(e,t)}static send(e){"sharedWorker"===o.currentMode&&o.workerManager?o.workerManager.send(e):o.client?o.client.send(e):this.logger.warn("[MessageSocket] 无可用连接,无法发送消息")}static getReadyState(){return o.client?.getReadyState()??WebSocket.CLOSED}static isConnected(){return"sharedWorker"===o.currentMode&&o.workerManager?o.workerManager.isConnected():o.client?.isConnected()??!1}static getConnectionMode(){return o.currentMode}static getCurrentUserId(){return o.currentUserId}static getCurrentToken(){return o.currentToken}}o.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,callbacks:[],heartbeatMessage:()=>({type:"PING",timestamp:Date.now()}),logLevel:"warn",enableVisibilityManagement:!1,connectionMode:"auto",sharedWorkerIdleTimeout:3e4},o.client=null,o.workerManager=null,o.currentMode="normal",o.logger=new t("MessageSocket",o.DEFAULT_CONFIG.logLevel),o.currentUserId=null,o.currentToken=null,o.config={...o.DEFAULT_CONFIG},o.visibilityListenerInitialized=!1,o.handleVisibilityChange=()=>{if("undefined"==typeof document)return;!document.hidden?o.currentUserId&&o.currentToken&&(o.logger.info("[MessageSocket] 页面可见,尝试重新连接"),o.client&&o.client.isConnected()||o.start({userId:o.currentUserId,token:o.currentToken})):(o.logger.info("[MessageSocket] 页面不可见,断开连接"),o.client&&o.client.disconnect())},exports.Logger=t,exports.MessageSocket=o,exports.SharedWorkerManager=n,exports.WebSocketClient=r;
1
+ "use strict";const e={debug:10,info:20,warn:30,error:40,silent:50};class t{constructor(e,t="warn"){this.name=e,this.level=t}setLevel(e){this.level=e}getLevel(){return this.level}debug(...e){this.logAtLevel("debug",console.debug,e)}info(...e){this.logAtLevel("info",console.info,e)}warn(...e){this.logAtLevel("warn",console.warn,e)}error(...e){this.logAtLevel("error",console.error,e)}shouldLog(t){return"silent"!==t&&e[t]>=e[this.level]}logAtLevel(e,t,n){this.shouldLog(e)&&t(`[${this.name}]`,...n)}}class n{constructor(e={}){this.socket=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.callbackList=[],this.currentUrl=null,this.reconnectAttempts=0,this.manualClose=!1,this.networkListenerInitialized=!1,this.handleOnline=()=>{this.logger.info("[WebSocketClient] 网络已恢复"),this.manualClose||this.isConnected()||(this.reconnectAttempts=0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.currentUrl&&this.config.autoReconnect&&(this.logger.info("[WebSocketClient] 网络恢复,立即尝试重连"),this.connect(this.currentUrl)))},this.handleOffline=()=>{this.logger.info("[WebSocketClient] 网络已断开"),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null)},this.config={...n.DEFAULT_CONFIG,...e},this.logger=new t("WebSocketClient",this.config.logLevel)}updateConfig(e){this.config={...this.config,...e},this.logger.setLevel(this.config.logLevel??n.DEFAULT_CONFIG.logLevel)}connect(e){const t=e||this.config.url;if(t){this.currentUrl=t,this.manualClose=!1,this.initNetworkListener();try{this.socket=new WebSocket(t),this.socket.onopen=()=>{this.logger.info("[WebSocketClient] 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),this.onOpen()},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{this.logger.info("[WebSocketClient] 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.onClose(e),!this.manualClose&&this.config.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{this.logger.error("[WebSocketClient] 连接错误",e),this.stopHeartbeat(),this.onError(e)}}catch(e){this.logger.error("[WebSocketClient] 连接失败",e),this.config.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else this.logger.error("[WebSocketClient] 缺少 WebSocket URL")}scheduleReconnect(){if(this.reconnectAttempts>=this.config.maxReconnectAttempts||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=this.config.maxReconnectAttempts&&this.logger.warn("[WebSocketClient] 已达到最大重连次数"));this.reconnectAttempts+=1;const e=Math.min(this.config.reconnectDelay*this.reconnectAttempts,this.config.reconnectDelayMax);this.logger.debug(`[WebSocketClient] 将在 ${e}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=window.setTimeout(()=>{this.connect(this.currentUrl)},e)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.removeNetworkListener(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.currentUrl=null,this.reconnectAttempts=0}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void this.logger.warn("[WebSocketClient] WebSocket 未连接,无法发送消息");const t="string"==typeof e?e:JSON.stringify(e);this.socket.send(t)}handleIncoming(e){if(!e)return;let t;try{t=JSON.parse(e)}catch{return void this.logger.warn("[WebSocketClient] 无法解析消息",e)}if(!t?.type)return;this.callbackList.filter(e=>e.type===t.type).forEach(({callback:e})=>{try{e(t.data,t)}catch(e){this.logger.error("[WebSocketClient] 回调执行失败",e)}}),this.onMessage(t)}startHeartbeat(){this.stopHeartbeat(),this.socket&&this.socket.readyState===WebSocket.OPEN&&(this.heartbeatTimer=window.setInterval(()=>{if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return;const e=this.config.heartbeatMessage();this.send(e)},this.config.heartbeatInterval))}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}on(e,t){const n="string"==typeof e?{type:e,callback:t}:e;n.type&&"function"==typeof n.callback?this.callbackList.push(n):this.logger.warn("[WebSocketClient] 无效的回调配置",n)}off(e,t){this.callbackList=t?this.callbackList.filter(n=>!(n.type===e&&n.callback===t)):this.callbackList.filter(t=>t.type!==e)}clearCallbacks(){this.callbackList=[]}getReadyState(){return this.socket?.readyState??WebSocket.CLOSED}isConnected(){return this.socket?.readyState===WebSocket.OPEN}onOpen(){this.logger.info("[WebSocketClient] 连接打开")}onClose(e){this.logger.info("[WebSocketClient] 连接打开")}onError(e){this.logger.error("[WebSocketClient] 连接错误",e)}onMessage(e){this.logger.debug("[WebSocketClient] 收到消息",e)}initNetworkListener(){"undefined"==typeof window||this.networkListenerInitialized||this.config.enableNetworkListener&&(window.addEventListener("online",this.handleOnline),window.addEventListener("offline",this.handleOffline),this.networkListenerInitialized=!0,this.logger.debug("[WebSocketClient] 已初始化网络状态监听"))}removeNetworkListener(){"undefined"!=typeof window&&this.networkListenerInitialized&&(window.removeEventListener("online",this.handleOnline),window.removeEventListener("offline",this.handleOffline),this.networkListenerInitialized=!1,this.logger.debug("[WebSocketClient] 已移除网络状态监听"))}}n.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,heartbeatMessage:()=>({type:"PING",timestamp:Date.now()}),logLevel:"warn",enableNetworkListener:!0};class r{constructor(e){this.worker=null,this.port=null,this.callbacks=new Map,this.callbackIdCounter=0,this.connected=!1,this.visibilityListenerInitialized=!1,this.onConnectedCallback=null,this.onDisconnectedCallback=null,this.onErrorCallback=null,this.onAuthConflictCallback=null,this.pingTimer=null,this.unloadListenerInitialized=!1,this.networkListenerInitialized=!1,this.handleVisibilityChange=()=>{const e=!document.hidden;if(this.logger.debug(`[SharedWorkerManager] 页面可见性变化: ${e}`),this.port){const t={isVisible:e};this.sendToWorker("TAB_VISIBILITY",t)}},this.handlePageHide=()=>{this.port&&this.sendToWorker("TAB_DISCONNECT",{})},this.handleOnline=()=>{this.logger.info("[SharedWorkerManager] 网络已恢复,通知 Worker 重连"),this.port&&this.sendToWorker("TAB_NETWORK_ONLINE",{})},this.handleOffline=()=>{this.logger.info("[SharedWorkerManager] 网络已断开")},this.config=e,this.tabId=this.generateTabId(),this.logger=new t("SharedWorkerManager",e.logLevel??"warn")}generateTabId(){return`tab_${Date.now()}_${Math.random().toString(36).substring(2,9)}`}async sendResetToExistingWorker(e){try{const t=new SharedWorker(e,{name:r.WORKER_NAME}).port;t.start(),t.postMessage({type:"TAB_FORCE_RESET",payload:{reason:"force_new_start"},tabId:this.tabId,timestamp:Date.now()}),await new Promise(e=>setTimeout(e,100)),t.close(),this.logger.debug("[SharedWorkerManager] 已发送重置命令到现有 Worker")}catch(e){this.logger.warn("[SharedWorkerManager] 发送重置命令失败(可忽略)",e)}}async start(){try{this.logger.debug("[SharedWorkerManager] 开始启动 SharedWorker");const e=this.getWorkerScriptDataUrl();this.logger.debug(`[SharedWorkerManager] Worker Script URL 创建成功: ${e.slice(0,60)}...`),this.config.forceNewWorkerOnStart&&(this.logger.debug("[SharedWorkerManager] forceNewWorkerOnStart=true,发送重置命令"),await this.sendResetToExistingWorker(e)),this.logger.debug("[SharedWorkerManager] 正在创建 SharedWorker 实例..."),this.worker=new SharedWorker(e,{name:r.WORKER_NAME}),this.logger.debug("[SharedWorkerManager] ✅ SharedWorker 实例创建成功"),this.port=this.worker.port,this.port.onmessage=this.handleWorkerMessage.bind(this),this.port.start(),this.logger.debug("[SharedWorkerManager] ✅ MessagePort 已启动"),this.setupVisibilityListener(),this.setupUnloadListener(),this.setupNetworkListener();const t={heartbeatInterval:this.config.config.heartbeatInterval,maxReconnectAttempts:this.config.config.maxReconnectAttempts,reconnectDelay:this.config.config.reconnectDelay,reconnectDelayMax:this.config.config.reconnectDelayMax,autoReconnect:this.config.config.autoReconnect,logLevel:this.config.config.logLevel};return this.sendToWorker("TAB_INIT",{url:this.config.url,userId:this.config.userId,token:this.config.token,isVisible:!document.hidden,config:t,sharedWorkerIdleTimeout:this.config.sharedWorkerIdleTimeout}),this.startPing(),this.logger.info("[SharedWorkerManager] SharedWorker 已启动"),!0}catch(e){return this.logger.error("[SharedWorkerManager] 启动 SharedWorker 失败",e),!1}}stop(){this.logger.debug("[SharedWorkerManager] 停止当前标签页的 SharedWorker 连接");const e=this.port;this.port&&this.sendToWorker("TAB_DISCONNECT",{}),this.stopPing(),this.removeVisibilityListener(),this.removeUnloadListener(),this.removeNetworkListener(),this.port=null,this.worker=null,this.connected=!1,this.callbacks.clear(),e&&setTimeout(()=>{try{e.close()}catch{}},100)}forceShutdown(){this.logger.debug("[SharedWorkerManager] 强制关闭 Worker(退出登录)");const e=this.port;this.port&&this.sendToWorker("TAB_FORCE_SHUTDOWN",{reason:"logout"}),this.stopPing(),this.removeVisibilityListener(),this.removeUnloadListener(),this.removeNetworkListener(),this.port=null,this.worker=null,this.connected=!1,this.callbacks.clear(),e&&setTimeout(()=>{try{e.close()}catch{}},100)}send(e){if(!this.port)return void this.logger.warn("[SharedWorkerManager] MessagePort 未初始化,无法发送消息");const t={data:e};this.sendToWorker("TAB_SEND",t)}registerCallback(e){const t="callback_"+this.callbackIdCounter++,n={...e,id:t};if(this.callbacks.set(t,n),this.port){const n={type:e.type,callbackId:t};this.sendToWorker("TAB_REGISTER_CALLBACK",n),this.logger.debug(`[SharedWorkerManager] ✅ 已发送注册消息到 Worker: ${e.type} (${t})`)}else this.logger.warn(`[SharedWorkerManager] ⚠️ port 未初始化,无法发送注册消息: ${e.type}`);return this.logger.debug(`[SharedWorkerManager] 注册回调: ${e.type} (${t}), 当前回调总数: ${this.callbacks.size}`),t}unregisterCallback(e,t){if(t){let n=null;for(const[r,s]of this.callbacks.entries())if(s.type===e&&s.callback===t){n=r,this.callbacks.delete(r);break}if(n&&this.port){const t={type:e,callbackId:n};this.sendToWorker("TAB_UNREGISTER_CALLBACK",t)}this.logger.debug(`[SharedWorkerManager] 取消注册回调: ${e} (${n})`)}else{const t=[];for(const[n,r]of this.callbacks.entries())r.type===e&&(t.push(n),this.callbacks.delete(n));if(this.port){const t={type:e};this.sendToWorker("TAB_UNREGISTER_CALLBACK",t)}this.logger.debug(`[SharedWorkerManager] 取消注册所有 ${e} 类型回调 (${t.length} 个)`)}}clearCallbacks(){for(const e of this.callbacks.values())if(this.port){const t={type:e.type,callbackId:e.id};this.sendToWorker("TAB_UNREGISTER_CALLBACK",t)}this.callbacks.clear(),this.logger.debug("[SharedWorkerManager] 已清空所有回调")}isConnected(){return this.connected}onConnected(e){this.onConnectedCallback=e}onDisconnected(e){this.onDisconnectedCallback=e}onError(e){this.onErrorCallback=e}onAuthConflict(e){this.onAuthConflictCallback=e}handleWorkerMessage(e){const t=e.data;switch(this.logger.debug(`[SharedWorkerManager] 📬 收到 Worker 消息, type: ${t.type}`),t.type){case"WORKER_CONNECTED":this.connected=!0,this.logger.info("[SharedWorkerManager] ✅ WebSocket 已连接"),this.onConnectedCallback?.();break;case"WORKER_DISCONNECTED":this.connected=!1,this.logger.info("[SharedWorkerManager] WebSocket 已断开"),this.onDisconnectedCallback?.();break;case"WORKER_MESSAGE":try{const e=t.payload;console.log("[SharedWorkerManager] 📨 收到服务器消息(经 Worker 转发):",e?.message),this.logger.info("[SharedWorkerManager] 📨 收到服务器消息(经 Worker 转发)",e?.message),this.logger.debug("[SharedWorkerManager] 🧾 原始消息 data:",e?.data)}catch(e){this.logger.warn("[SharedWorkerManager] 打印服务器消息失败",e)}this.handleServerMessage(t.payload);break;case"WORKER_ERROR":this.logger.error("[SharedWorkerManager] Worker 错误",t.payload),this.onErrorCallback?.(t.payload);break;case"WORKER_AUTH_CONFLICT":this.logger.warn("[SharedWorkerManager] 身份冲突",t.payload),this.onAuthConflictCallback?.(t.payload);break;case"WORKER_PONG":this.logger.debug("[SharedWorkerManager] 收到 PONG");break;default:this.logger.warn("[SharedWorkerManager] 未知消息类型",t.type)}}handleServerMessage(e){const{message:t}=e;this.logger.debug(`[SharedWorkerManager] 📨 收到服务器消息, type: ${t.type}`),this.logger.debug("[SharedWorkerManager] 当前注册的回调类型:",Array.from(this.callbacks.values()).map(e=>e.type));let n=0;for(const e of this.callbacks.values())if(e.type===t.type){n++,this.logger.debug(`[SharedWorkerManager] ✅ 匹配到回调 ${e.type} (${e.id}),准备执行`);try{e.callback(t.data,t),this.logger.debug(`[SharedWorkerManager] ✅ 回调执行成功 ${e.type} (${e.id})`)}catch(e){this.logger.error("[SharedWorkerManager] ❌ 回调执行失败",e)}}0===n&&this.logger.warn(`[SharedWorkerManager] ⚠️ 没有匹配的回调: ${t.type}`)}sendToWorker(e,t){if(!this.port)return void this.logger.warn("[SharedWorkerManager] MessagePort 未初始化,无法发送消息");const n={type:e,payload:t,tabId:this.tabId,timestamp:Date.now()};try{this.port.postMessage(n)}catch(e){this.logger.error("[SharedWorkerManager] 发送消息到 Worker 失败",e)}}setupVisibilityListener(){"undefined"==typeof document||this.visibilityListenerInitialized||(document.addEventListener("visibilitychange",this.handleVisibilityChange),this.visibilityListenerInitialized=!0,this.logger.debug("[SharedWorkerManager] 已设置页面可见性监听"))}removeVisibilityListener(){"undefined"!=typeof document&&this.visibilityListenerInitialized&&(document.removeEventListener("visibilitychange",this.handleVisibilityChange),this.visibilityListenerInitialized=!1,this.logger.debug("[SharedWorkerManager] 已移除页面可见性监听"))}startPing(){this.stopPing(),"undefined"!=typeof window&&this.port&&(this.pingTimer=globalThis.setInterval(()=>{this.port&&this.sendToWorker("TAB_PING",{})},1e4))}stopPing(){null!==this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null)}setupUnloadListener(){"undefined"==typeof window||this.unloadListenerInitialized||(window.addEventListener("pagehide",this.handlePageHide,{capture:!0}),window.addEventListener("beforeunload",this.handlePageHide,{capture:!0}),this.unloadListenerInitialized=!0,this.logger.debug("[SharedWorkerManager] 已设置页面卸载监听"))}removeUnloadListener(){"undefined"!=typeof window&&this.unloadListenerInitialized&&(window.removeEventListener("pagehide",this.handlePageHide,{capture:!0}),window.removeEventListener("beforeunload",this.handlePageHide,{capture:!0}),this.unloadListenerInitialized=!1,this.logger.debug("[SharedWorkerManager] 已移除页面卸载监听"))}setupNetworkListener(){"undefined"==typeof window||this.networkListenerInitialized||(window.addEventListener("online",this.handleOnline),window.addEventListener("offline",this.handleOffline),this.networkListenerInitialized=!0,this.logger.debug("[SharedWorkerManager] 已设置网络状态监听"))}removeNetworkListener(){"undefined"!=typeof window&&this.networkListenerInitialized&&(window.removeEventListener("online",this.handleOnline),window.removeEventListener("offline",this.handleOffline),this.networkListenerInitialized=!1,this.logger.debug("[SharedWorkerManager] 已移除网络状态监听"))}getWorkerScriptDataUrl(){const e=this.getWorkerScriptContent();this.logger.debug(`[SharedWorkerManager] 正在创建 Worker Data URL, 代码长度: ${e.length}`);return`data:application/javascript;charset=utf-8;base64,${this.toBase64Utf8(e)}`}toBase64Utf8(e){try{if("undefined"!=typeof TextEncoder){const t=(new TextEncoder).encode(e);let n="";for(let e=0;e<t.length;e++)n+=String.fromCharCode(t[e]);return btoa(n)}return btoa(unescape(encodeURIComponent(e)))}catch(e){throw this.logger.error("[SharedWorkerManager] ❌ Worker 脚本 base64 编码失败",e),e}}getWorkerScriptContent(){const e="const WorkerToTabMessageType = {\n WORKER_READY: 'WORKER_READY',\n WORKER_MESSAGE: 'WORKER_MESSAGE',\n WORKER_CONNECTED: 'WORKER_CONNECTED',\n WORKER_DISCONNECTED: 'WORKER_DISCONNECTED',\n WORKER_ERROR: 'WORKER_ERROR',\n WORKER_AUTH_CONFLICT: 'WORKER_AUTH_CONFLICT',\n WORKER_PONG: 'WORKER_PONG',\n};\nclass WebSocketManager {\n constructor() {\n this.tabs = new Map();\n this.socket = null;\n this.lastMessageByType = new Map();\n this.tabCleanupTimer = null;\n this.idleTimer = null;\n this.heartbeatTimer = null;\n this.reconnectTimer = null;\n this.reconnectAttempts = 0;\n this.manualClose = false;\n this.currentUrl = null;\n this.currentUserId = null;\n this.currentToken = null;\n this.currentBaseUrl = null;\n this.lastOpenAt = 0;\n this.fastClose1000Count = 0;\n this.reconnectSuppressedUntil = 0;\n this.config = null;\n this.sharedWorkerIdleTimeout = 30000;\n this.tabStaleTimeout = 45000;\n }\n hasVisibleTab() {\n return Array.from(this.tabs.values()).some((tab) => tab.isVisible);\n }\n clearReconnectTimer() {\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n startTabCleanup() {\n if (this.tabCleanupTimer !== null)\n return;\n this.tabCleanupTimer = globalThis.setInterval(() => {\n const now = Date.now();\n const staleTabIds = [];\n for (const tab of this.tabs.values()) {\n if (now - tab.lastSeen > this.tabStaleTimeout) {\n staleTabIds.push(tab.tabId);\n }\n }\n if (staleTabIds.length > 0) {\n console.warn('[SharedWorker] 检测到过期标签页,将清理:', staleTabIds);\n staleTabIds.forEach((id) => this.removeTab(id));\n }\n }, 15000);\n }\n stopTabCleanup() {\n if (this.tabCleanupTimer !== null) {\n clearInterval(this.tabCleanupTimer);\n this.tabCleanupTimer = null;\n }\n }\n addTab(port, message) {\n const { tabId, payload } = message;\n const initPayload = payload;\n if (this.currentUserId && this.currentUserId !== initPayload.userId) {\n const conflictPayload = {\n currentUserId: this.currentUserId,\n newUserId: initPayload.userId,\n message: `检测到不同用户身份:当前连接用户为 ${this.currentUserId},新标签页尝试使用用户 ${initPayload.userId} 连接。将复用现有连接。`,\n };\n this.sendToTab(port, WorkerToTabMessageType.WORKER_AUTH_CONFLICT, conflictPayload);\n console.warn('[SharedWorker]', conflictPayload.message);\n }\n const tabInfo = {\n port,\n tabId,\n isVisible: initPayload.isVisible,\n lastSeen: Date.now(),\n registeredTypes: new Set(),\n callbackMap: new Map(),\n };\n this.tabs.set(tabId, tabInfo);\n console.log(`[SharedWorker] 标签页已添加: ${tabId}, 当前标签页数量: ${this.tabs.size}`);\n this.startTabCleanup();\n const nextBaseUrl = initPayload.url;\n const nextUserId = initPayload.userId;\n const nextToken = initPayload.token;\n const nextUrl = `${nextBaseUrl}/${nextUserId}?token=${encodeURIComponent(nextToken)}`;\n const hasExistingIdentity = this.currentBaseUrl !== null ||\n this.currentUserId !== null ||\n this.currentToken !== null ||\n this.currentUrl !== null;\n const identityChanged = (this.currentBaseUrl !== null && this.currentBaseUrl !== nextBaseUrl) ||\n (this.currentUserId !== null && this.currentUserId !== nextUserId) ||\n (this.currentToken !== null && this.currentToken !== nextToken) ||\n (this.currentUrl !== null && this.currentUrl !== nextUrl);\n if (!hasExistingIdentity || identityChanged) {\n if (hasExistingIdentity) {\n const conflictPayload = {\n currentUserId: this.currentUserId ?? '',\n newUserId: nextUserId,\n message: `检测到连接身份/参数变化:` +\n `oldUser=${this.currentUserId ?? 'null'} -> newUser=${nextUserId}, ` +\n `oldBaseUrl=${this.currentBaseUrl ?? 'null'} -> newBaseUrl=${nextBaseUrl}, ` +\n `tokenChanged=${this.currentToken ? this.currentToken !== nextToken : true}。` +\n `将切换到最新登录态并重建连接。`,\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_AUTH_CONFLICT, conflictPayload);\n console.warn('[SharedWorker]', conflictPayload.message);\n }\n this.currentBaseUrl = nextBaseUrl;\n this.currentUserId = nextUserId;\n this.currentToken = nextToken;\n this.currentUrl = nextUrl;\n this.config = initPayload.config;\n this.sharedWorkerIdleTimeout = initPayload.sharedWorkerIdleTimeout ?? 30000;\n this.lastMessageByType.clear();\n if (this.socket) {\n console.log('[SharedWorker] 检测到登录态变化,断开旧连接以使用新参数');\n this.disconnect();\n }\n }\n else {\n this.config = initPayload.config;\n this.sharedWorkerIdleTimeout = initPayload.sharedWorkerIdleTimeout ?? this.sharedWorkerIdleTimeout;\n }\n if (this.socket && this.socket.readyState === WebSocket.OPEN) {\n this.sendToTab(port, WorkerToTabMessageType.WORKER_CONNECTED, {});\n }\n this.checkAllTabsVisibility();\n }\n removeTab(tabId) {\n this.tabs.delete(tabId);\n console.log(`[SharedWorker] 标签页已移除: ${tabId}, 剩余标签页数量: ${this.tabs.size}`);\n if (this.tabs.size === 0) {\n console.log(`[SharedWorker] 所有标签页已关闭,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`);\n this.clearReconnectTimer();\n this.stopTabCleanup();\n this.startIdleTimer();\n }\n else {\n this.checkAllTabsVisibility();\n }\n }\n updateTabVisibility(tabId, isVisible) {\n const tab = this.tabs.get(tabId);\n if (!tab) {\n console.warn(`[SharedWorker] 标签页不存在: ${tabId}`);\n return;\n }\n tab.isVisible = isVisible;\n tab.lastSeen = Date.now();\n console.log(`[SharedWorker] 标签页 ${tabId} 可见性更新: ${isVisible}`);\n this.checkAllTabsVisibility();\n }\n checkAllTabsVisibility() {\n if (this.tabs.size === 0)\n return;\n const allHidden = Array.from(this.tabs.values()).every((tab) => !tab.isVisible);\n if (allHidden) {\n console.log(`[SharedWorker] 所有标签页都不可见,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`);\n this.clearReconnectTimer();\n this.startIdleTimer();\n }\n else {\n console.log('[SharedWorker] 至少有一个标签页可见,保持连接');\n this.resetIdleTimer();\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n if (this.currentUrl) {\n console.log('[SharedWorker] 检测到连接已断开,标签页可见,尝试重新连接');\n this.connect();\n }\n }\n }\n }\n startIdleTimer() {\n this.clearIdleTimer();\n this.idleTimer = globalThis.setTimeout(() => {\n console.log('[SharedWorker] 空闲超时,断开连接');\n this.disconnect();\n }, this.sharedWorkerIdleTimeout);\n }\n resetIdleTimer() {\n this.clearIdleTimer();\n }\n clearIdleTimer() {\n if (this.idleTimer !== null) {\n clearTimeout(this.idleTimer);\n this.idleTimer = null;\n }\n }\n connect() {\n if (!this.currentUrl) {\n console.error('[SharedWorker] 缺少 WebSocket URL');\n return;\n }\n if (Date.now() < this.reconnectSuppressedUntil) {\n console.warn('[SharedWorker] 自动重连已熔断,暂不连接', {\n suppressedUntil: this.reconnectSuppressedUntil,\n });\n return;\n }\n if (!this.hasVisibleTab()) {\n console.log('[SharedWorker] 当前无可见标签页,跳过连接');\n return;\n }\n if (this.socket && (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING)) {\n return;\n }\n this.manualClose = false;\n this.clearReconnectTimer();\n try {\n this.socket = new WebSocket(this.currentUrl);\n this.socket.onopen = () => {\n console.log('[SharedWorker] ✅ WebSocket 连接成功');\n this.reconnectAttempts = 0;\n this.lastOpenAt = Date.now();\n this.fastClose1000Count = 0;\n this.startHeartbeat();\n console.log(`[SharedWorker] 通知 ${this.tabs.size} 个标签页: 已连接`);\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_CONNECTED, {});\n };\n this.socket.onmessage = (event) => {\n this.handleIncoming(event.data);\n };\n this.socket.onclose = (event) => {\n const now = Date.now();\n const liveMs = this.lastOpenAt ? now - this.lastOpenAt : -1;\n console.log('[SharedWorker] WebSocket 连接关闭', {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean,\n liveMs,\n });\n this.stopHeartbeat();\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_DISCONNECTED, {});\n if (event.code === 1000 && liveMs >= 0 && liveMs < 3000) {\n this.fastClose1000Count += 1;\n console.warn('[SharedWorker] 检测到快速 1000 关闭', {\n fastClose1000Count: this.fastClose1000Count,\n liveMs,\n });\n if (this.fastClose1000Count >= 3) {\n this.reconnectSuppressedUntil = now + 60000;\n const errorPayload = {\n message: 'WebSocket 被服务端频繁正常关闭(1000),已临时暂停自动重连 60s。请检查 token/服务端是否限制同账号多连接/是否需要额外鉴权消息。',\n error: {\n code: event.code,\n reason: event.reason,\n liveMs,\n },\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload);\n return;\n }\n }\n else {\n this.fastClose1000Count = 0;\n }\n if (!this.manualClose && this.config?.autoReconnect && this.tabs.size > 0 && this.hasVisibleTab()) {\n this.scheduleReconnect();\n }\n };\n this.socket.onerror = (event) => {\n console.error('[SharedWorker] WebSocket 连接错误', event);\n this.stopHeartbeat();\n const errorPayload = {\n message: 'WebSocket 连接错误',\n error: event,\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload);\n };\n }\n catch (error) {\n console.error('[SharedWorker] 创建 WebSocket 连接失败', error);\n const errorPayload = {\n message: '创建 WebSocket 连接失败',\n error,\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload);\n if (this.config?.autoReconnect && !this.manualClose) {\n this.scheduleReconnect();\n }\n }\n }\n scheduleReconnect() {\n if (this.tabs.size === 0 || !this.hasVisibleTab()) {\n return;\n }\n const maxAttempts = this.config?.maxReconnectAttempts ?? 10;\n const reconnectDelay = this.config?.reconnectDelay ?? 3000;\n const reconnectDelayMax = this.config?.reconnectDelayMax ?? 10000;\n if (this.reconnectAttempts >= maxAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= maxAttempts) {\n console.warn('[SharedWorker] 已达到最大重连次数');\n }\n return;\n }\n this.reconnectAttempts += 1;\n const delay = Math.min(reconnectDelay * this.reconnectAttempts, reconnectDelayMax);\n console.log(`[SharedWorker] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`);\n this.clearReconnectTimer();\n this.reconnectTimer = globalThis.setTimeout(() => {\n this.connect();\n }, delay);\n }\n disconnect() {\n this.manualClose = true;\n this.stopHeartbeat();\n this.clearIdleTimer();\n this.clearReconnectTimer();\n if (this.socket) {\n this.socket.close();\n this.socket = null;\n }\n this.reconnectAttempts = 0;\n }\n startHeartbeat() {\n this.stopHeartbeat();\n const heartbeatInterval = this.config?.heartbeatInterval ?? 25000;\n this.heartbeatTimer = globalThis.setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return;\n }\n const heartbeatData = { type: 'PING', timestamp: Date.now() };\n this.send(heartbeatData);\n }, heartbeatInterval);\n }\n stopHeartbeat() {\n if (this.heartbeatTimer !== null) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n send(data) {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n console.warn('[SharedWorker] WebSocket 未连接,无法发送消息');\n return;\n }\n const message = typeof data === 'string' ? data : JSON.stringify(data);\n this.socket.send(message);\n }\n handleIncoming(data) {\n if (!data)\n return;\n let message;\n try {\n message = JSON.parse(data);\n }\n catch {\n console.warn('[SharedWorker] 无法解析消息', data);\n return;\n }\n if (!message?.type) {\n return;\n }\n console.log(`[SharedWorker] 📨 收到服务器消息, type: ${message.type}`, message);\n const serverMessagePayload = {\n data,\n message,\n };\n this.lastMessageByType.set(message.type, serverMessagePayload);\n let sentCount = 0;\n for (const tab of this.tabs.values()) {\n console.log(`[SharedWorker] 检查标签页 ${tab.tabId}, 注册的类型:`, Array.from(tab.registeredTypes));\n if (tab.registeredTypes.has(message.type)) {\n console.log(`[SharedWorker] ✅ 发送消息到标签页 ${tab.tabId}, type: ${message.type}`);\n this.sendToTab(tab.port, WorkerToTabMessageType.WORKER_MESSAGE, serverMessagePayload);\n sentCount++;\n }\n }\n console.log(`[SharedWorker] 消息分发完成, type: ${message.type}, 发送给 ${sentCount} 个标签页`);\n }\n registerCallback(tabId, payload) {\n const tab = this.tabs.get(tabId);\n if (!tab) {\n console.warn(`[SharedWorker] ⚠️ 标签页不存在: ${tabId}`);\n return;\n }\n tab.lastSeen = Date.now();\n tab.registeredTypes.add(payload.type);\n tab.callbackMap.set(payload.callbackId, payload.type);\n console.log(`[SharedWorker] ✅ 标签页 ${tabId} 注册回调: ${payload.type} (${payload.callbackId})`);\n console.log(`[SharedWorker] 标签页 ${tabId} 当前注册的所有类型:`, Array.from(tab.registeredTypes));\n const cached = this.lastMessageByType.get(payload.type);\n if (cached) {\n console.log(`[SharedWorker] 🔁 回放缓存消息到标签页 ${tabId}, type: ${payload.type}`);\n this.sendToTab(tab.port, WorkerToTabMessageType.WORKER_MESSAGE, cached);\n }\n }\n unregisterCallback(tabId, payload) {\n const tab = this.tabs.get(tabId);\n if (!tab) {\n console.warn(`[SharedWorker] 标签页不存在: ${tabId}`);\n return;\n }\n tab.lastSeen = Date.now();\n if (payload.callbackId) {\n const type = tab.callbackMap.get(payload.callbackId);\n if (type) {\n tab.callbackMap.delete(payload.callbackId);\n const hasOtherCallbacks = Array.from(tab.callbackMap.values()).some((t) => t === type);\n if (!hasOtherCallbacks) {\n tab.registeredTypes.delete(type);\n }\n console.log(`[SharedWorker] 标签页 ${tabId} 取消注册回调: ${type}`);\n }\n }\n else {\n tab.registeredTypes.delete(payload.type);\n for (const [callbackId, type] of tab.callbackMap.entries()) {\n if (type === payload.type) {\n tab.callbackMap.delete(callbackId);\n }\n }\n console.log(`[SharedWorker] 标签页 ${tabId} 取消注册所有 ${payload.type} 类型回调`);\n }\n }\n sendToTab(port, type, payload) {\n const message = {\n type,\n payload,\n timestamp: Date.now(),\n };\n try {\n port.postMessage(message);\n }\n catch (error) {\n console.error('[SharedWorker] 发送消息到标签页失败', error);\n }\n }\n broadcastToAllTabs(type, payload) {\n for (const tab of this.tabs.values()) {\n this.sendToTab(tab.port, type, payload);\n }\n }\n forceReset(reason) {\n console.warn('[SharedWorker] 🔄 收到强制重置指令,正在重置 Worker 状态', { reason });\n this.disconnect();\n this.lastMessageByType.clear();\n this.currentUrl = null;\n this.currentBaseUrl = null;\n this.currentUserId = null;\n this.currentToken = null;\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_DISCONNECTED, {});\n console.log('[SharedWorker] ✅ Worker 状态已重置,等待新的连接参数');\n }\n forceShutdown(reason) {\n console.warn('[SharedWorker] ⚠️ 收到强制关闭指令,正在关闭 Worker', { reason });\n this.disconnect();\n this.lastMessageByType.clear();\n this.currentUrl = null;\n this.currentBaseUrl = null;\n this.currentUserId = null;\n this.currentToken = null;\n for (const tab of this.tabs.values()) {\n try {\n tab.port.postMessage({\n type: WorkerToTabMessageType.WORKER_DISCONNECTED,\n payload: {},\n timestamp: Date.now(),\n });\n }\n catch {\n }\n try {\n tab.port.close();\n }\n catch {\n }\n }\n this.tabs.clear();\n this.stopTabCleanup();\n const workerClose = globalThis.close;\n if (typeof workerClose === 'function') {\n console.warn('[SharedWorker] 🛑 正在终止 SharedWorker 进程');\n try {\n workerClose();\n }\n catch (error) {\n console.warn('[SharedWorker] 终止 SharedWorker 失败', error);\n }\n }\n }\n handleNetworkOnline() {\n console.log('[SharedWorker] 🌐 收到网络恢复通知');\n if (this.socket && this.socket.readyState === WebSocket.OPEN) {\n console.log('[SharedWorker] 已有活跃连接,无需重连');\n return;\n }\n this.reconnectAttempts = 0;\n this.clearReconnectTimer();\n this.reconnectSuppressedUntil = 0;\n if (this.currentUrl && this.hasVisibleTab()) {\n console.log('[SharedWorker] 网络恢复,立即尝试重连');\n this.connect();\n }\n else {\n console.log('[SharedWorker] 网络恢复,但无可见标签页或无 URL,等待条件满足');\n }\n }\n}\nconst wsManager = new WebSocketManager();\nglobalThis.onconnect = (event) => {\n const port = event.ports[0];\n port.onmessage = (e) => {\n const message = e.data;\n console.log(`[SharedWorker] 📬 收到标签页消息, type: ${message.type}, tabId: ${message.tabId}`);\n const tab = wsManager.tabs?.get?.(message.tabId);\n if (tab)\n tab.lastSeen = Date.now();\n switch (message.type) {\n case 'TAB_INIT':\n wsManager.addTab(port, message);\n break;\n case 'TAB_SEND':\n wsManager.send(message.payload.data);\n break;\n case 'TAB_VISIBILITY':\n wsManager.updateTabVisibility(message.tabId, message.payload.isVisible);\n break;\n case 'TAB_REGISTER_CALLBACK':\n console.log(`[SharedWorker] 🔔 处理注册回调请求:`, message.payload);\n wsManager.registerCallback(message.tabId, message.payload);\n break;\n case 'TAB_UNREGISTER_CALLBACK':\n wsManager.unregisterCallback(message.tabId, message.payload);\n break;\n case 'TAB_DISCONNECT':\n wsManager.removeTab(message.tabId);\n break;\n case 'TAB_PING':\n port.postMessage({\n type: 'WORKER_PONG',\n timestamp: Date.now(),\n });\n break;\n case 'TAB_FORCE_RESET':\n wsManager.forceReset(message.payload?.reason);\n break;\n case 'TAB_FORCE_SHUTDOWN':\n wsManager.forceShutdown(message.payload?.reason);\n break;\n case 'TAB_NETWORK_ONLINE':\n wsManager.handleNetworkOnline();\n break;\n default:\n console.warn('[SharedWorker] 未知消息类型', message.type);\n }\n };\n port.start();\n};\n";return this.logger.debug("[SharedWorkerManager] Worker 脚本长度: 21587 字符"),e}}r.WORKER_NAME="dj-common-websocket-worker";class s{static updateLoggerLevel(){s.logger.setLevel(s.config.logLevel??s.DEFAULT_CONFIG.logLevel)}static initVisibilityListener(){"undefined"==typeof document||s.visibilityListenerInitialized||(document.addEventListener("visibilitychange",s.handleVisibilityChange),s.visibilityListenerInitialized=!0,s.logger.debug("[MessageSocket] 已初始化页面可见性监听"))}static removeVisibilityListener(){"undefined"!=typeof document&&s.visibilityListenerInitialized&&(document.removeEventListener("visibilitychange",s.handleVisibilityChange),s.visibilityListenerInitialized=!1,s.logger.debug("[MessageSocket] 已移除页面可见性监听"))}static detectSharedWorkerSupport(){return"undefined"!=typeof SharedWorker&&"undefined"!=typeof Blob}static detectVisibilitySupport(){return"undefined"!=typeof document&&void 0!==document.hidden}static determineConnectionMode(){const e=s.config.connectionMode||"auto";return"auto"===e?this.detectSharedWorkerSupport()?(s.logger.info("[MessageSocket] 自动选择 SharedWorker 模式"),"sharedWorker"):this.detectVisibilitySupport()&&s.config.enableVisibilityManagement?(s.logger.info("[MessageSocket] 自动选择 Visibility 模式"),"visibility"):(s.logger.info("[MessageSocket] 自动选择 Normal 模式"),"normal"):"sharedWorker"===e?this.detectSharedWorkerSupport()?(s.logger.info("[MessageSocket] 使用 SharedWorker 模式"),"sharedWorker"):(s.logger.warn("[MessageSocket] 浏览器不支持 SharedWorker,降级到 Visibility 模式"),this.detectVisibilitySupport()&&s.config.enableVisibilityManagement?"visibility":(s.logger.warn("[MessageSocket] 降级到 Normal 模式"),"normal")):"visibility"===e?this.detectVisibilitySupport()?(s.logger.info("[MessageSocket] 使用 Visibility 模式"),"visibility"):(s.logger.warn("[MessageSocket] 浏览器不支持 Visibility API,降级到 Normal 模式"),"normal"):(s.logger.info("[MessageSocket] 使用 Normal 模式"),"normal")}static configure(e){s.config={...s.config,...e}}static setConfig(e){return s.config={...s.config,...e},s.updateLoggerLevel(),s.config.callbacks&&s.config.callbacks.length>0&&s.setCallbacks(s.config.callbacks),s}static setCallbacks(e){return e&&0!==e.length?(s.config.callbacks=e,s):(this.logger.warn("[MessageSocket] 回调列表为空,无法设置回调"),s)}static start(e){if(!s.config.url)return void this.logger.error("[MessageSocket] 缺少配置 url, 请先调用 setConfig 设置配置!");const{userId:t,token:n}=e;t&&n?(this.logger.info("[MessageSocket] 开始连接",t),s.currentMode=s.determineConnectionMode(),"sharedWorker"===s.currentMode?s.startWithSharedWorker(e):"visibility"===s.currentMode?s.startWithVisibility(e):s.startWithNormalMode(e)):this.logger.error("[MessageSocket] 缺少 userId 或 token,无法启动")}static async startWithSharedWorker(e){const{userId:t,token:n}=e;s.stop(),s.currentUserId=t,s.currentToken=n,s.workerManager=new r({url:s.config.url,userId:t,token:n,isVisible:"undefined"==typeof document||!document.hidden,config:s.config,sharedWorkerIdleTimeout:s.config.sharedWorkerIdleTimeout,logLevel:s.config.logLevel,forceNewWorkerOnStart:s.config.forceNewWorkerOnStart}),s.workerManager.onError(t=>{s.logger.warn("[MessageSocket] SharedWorker 失败,降级到 Visibility 模式",t),s.currentMode="visibility",s.workerManager=null,s.startWithVisibility(e)});if(!await s.workerManager.start())return s.logger.warn("[MessageSocket] SharedWorker 启动失败,降级到 Visibility 模式"),s.currentMode="visibility",s.workerManager=null,void s.startWithVisibility(e);s.config.callbacks&&s.config.callbacks.length>0&&s.config.callbacks.forEach(e=>{s.workerManager.registerCallback(e)})}static startWithVisibility(e){const{userId:t,token:r}=e;if(s.client&&s.client.isConnected()&&s.currentUserId===t&&s.currentToken===r)return void this.logger.debug("[MessageSocket] 复用现有连接");s.stop(),s.currentUserId=t,s.currentToken=r;const{url:o,...a}=s.config,i=`${o}/${t}?token=${encodeURIComponent(r)}`;s.client=new n({...a,url:i}),s.config.callbacks&&s.config.callbacks.length>0&&s.config.callbacks.forEach(e=>s.client.on(e)),s.initVisibilityListener(),s.client.connect()}static startWithNormalMode(e){const{userId:t,token:r}=e;if(s.client&&s.client.isConnected()&&s.currentUserId===t&&s.currentToken===r)return void this.logger.debug("[MessageSocket] 复用现有连接");s.stop(),s.currentUserId=t,s.currentToken=r;const{url:o,...a}=s.config,i=`${o}/${t}?token=${encodeURIComponent(r)}`;s.client=new n({...a,url:i}),s.config.callbacks&&s.config.callbacks.length>0&&s.config.callbacks.forEach(e=>s.client.on(e)),s.client.connect()}static stop(){s.logger.info("[MessageSocket] 停止连接"),s.client&&(s.client.disconnect(),s.client.clearCallbacks(),s.client=null),s.workerManager&&(s.workerManager.stop(),s.workerManager=null),s.removeVisibilityListener(),s.currentUserId=null,s.currentToken=null}static registerCallbacks(e){e?"object"==typeof e?"function"==typeof e.callback?"string"==typeof e.type?"sharedWorker"===s.currentMode&&s.workerManager?s.workerManager.registerCallback(e):s.client?s.client.on(e):this.logger.warn("[MessageSocket] 无可用连接,无法注册回调"):this.logger.warn("[MessageSocket] 注册回调失败,type 不是字符串",e):this.logger.warn("[MessageSocket] 注册回调失败,callback 不是函数",e):this.logger.warn("[MessageSocket] 注册回调失败,entry 不是对象",e):this.logger.warn("[MessageSocket] 注册回调失败,缺少 entry",e)}static unregisterCallbacks(e,t){"sharedWorker"===s.currentMode&&s.workerManager?s.workerManager.unregisterCallback(e,t):s.client&&s.client.off(e,t)}static send(e){"sharedWorker"===s.currentMode&&s.workerManager?s.workerManager.send(e):s.client?s.client.send(e):this.logger.warn("[MessageSocket] 无可用连接,无法发送消息")}static getReadyState(){return s.client?.getReadyState()??WebSocket.CLOSED}static isConnected(){return"sharedWorker"===s.currentMode&&s.workerManager?s.workerManager.isConnected():s.client?.isConnected()??!1}static getConnectionMode(){return s.currentMode}static getCurrentUserId(){return s.currentUserId}static getCurrentToken(){return s.currentToken}}s.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,callbacks:[],heartbeatMessage:()=>({type:"PING",timestamp:Date.now()}),logLevel:"warn",enableVisibilityManagement:!1,connectionMode:"auto",sharedWorkerIdleTimeout:3e4,forceNewWorkerOnStart:!1,enableNetworkListener:!0},s.client=null,s.workerManager=null,s.currentMode="normal",s.logger=new t("MessageSocket",s.DEFAULT_CONFIG.logLevel),s.currentUserId=null,s.currentToken=null,s.config={...s.DEFAULT_CONFIG},s.visibilityListenerInitialized=!1,s.handleVisibilityChange=()=>{if("undefined"==typeof document)return;!document.hidden?s.currentUserId&&s.currentToken&&(s.logger.info("[MessageSocket1111111111111111111111] 页面可见,尝试重新连接"),s.client&&s.client.isConnected()||s.start({userId:s.currentUserId,token:s.currentToken})):(s.logger.info("[MessageSocket1111111111111111111111] 页面不可见,断开连接"),s.client&&s.client.disconnect())},exports.Logger=t,exports.MessageSocket=s,exports.SharedWorkerManager=r,exports.WebSocketClient=n;
2
2
  //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/logger.ts","../src/WebSocketClient.ts","../src/SharedWorkerManager.ts","../src/MessageSocket.ts"],"sourcesContent":["export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'\n\nexport const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n silent: 50,\n}\n\nexport class Logger {\n private level: LogLevel\n\n constructor(\n private readonly name: string,\n level: LogLevel = 'warn'\n ) {\n this.level = level\n }\n\n setLevel(level: LogLevel): void {\n this.level = level\n }\n\n getLevel(): LogLevel {\n return this.level\n }\n\n debug(...values: unknown[]): void {\n this.logAtLevel('debug', console.debug, values)\n }\n\n info(...values: unknown[]): void {\n this.logAtLevel('info', console.info, values)\n }\n\n warn(...values: unknown[]): void {\n this.logAtLevel('warn', console.warn, values)\n }\n\n error(...values: unknown[]): void {\n this.logAtLevel('error', console.error, values)\n }\n\n private shouldLog(level: LogLevel): boolean {\n if (level === 'silent') {\n return false\n }\n return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.level]\n }\n\n private logAtLevel(level: LogLevel, writer: (...args: unknown[]) => void, values: unknown[]): void {\n if (!this.shouldLog(level)) {\n return\n }\n writer(`[${this.name}]`, ...values)\n }\n}\n","import { Logger, LogLevel } from './logger'\n\n/**\n * WebSocket 配置选项\n */\nexport interface WebSocketConfig {\n /** WebSocket 服务器地址(可选,可以在 connect 时指定) */\n url?: string\n /** 心跳间隔(毫秒),默认 25000 */\n heartbeatInterval?: number\n /** 最大重连次数,默认 10 */\n maxReconnectAttempts?: number\n /** 重连延迟(毫秒),默认 3000 */\n reconnectDelay?: number\n /** 最大重连延迟(毫秒),默认 10000 */\n reconnectDelayMax?: number\n /** 心跳消息生成器 */\n heartbeatMessage?: () => string | object\n /** 是否自动重连,默认 true */\n autoReconnect?: boolean\n /** 日志输出等级 */\n logLevel?: LogLevel\n}\n\n/**\n * 消息数据结构\n */\nexport interface MessageData<T = unknown> {\n /** 消息类型 */\n type: string\n /** 消息数据 */\n data: T\n /** 元数据 */\n meta?: Record<string, unknown>\n /** 时间戳 */\n timestamp?: number\n}\n\n/**\n * 消息回调函数\n */\nexport type MessageCallback<T = unknown> = (data: T, message?: MessageData<T>) => void\n\n/**\n * 消息回调配置\n */\nexport interface MessageCallbackEntry<T = unknown> {\n /** 消息类型 */\n type: string\n /** 回调函数 */\n callback: MessageCallback<T>\n}\n\n/**\n * WebSocket 基础封装类\n * 提供连接管理、心跳、自动重连、消息回调等基础功能\n */\nexport class WebSocketClient {\n /** WebSocket 连接实例 */\n protected socket: WebSocket | null = null\n\n /** 心跳定时器 */\n protected heartbeatTimer: number | null = null\n\n /** 重连定时器 */\n protected reconnectTimer: number | null = null\n\n /** 消息回调列表 */\n protected callbackList: MessageCallbackEntry<unknown>[] = []\n\n /** 当前连接的 URL */\n protected currentUrl: string | null = null\n\n /** 配置选项 */\n protected config: Required<WebSocketConfig>\n\n /** 重连次数 */\n protected reconnectAttempts = 0\n\n /** 是否手动关闭 */\n protected manualClose = false\n\n /** 日志器 */\n protected readonly logger: Logger\n\n /**\n * 默认配置\n */\n protected static readonly DEFAULT_CONFIG: Required<WebSocketConfig> = {\n url: '',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n heartbeatMessage: () => ({\n type: 'PING',\n timestamp: Date.now(),\n }),\n logLevel: 'warn',\n }\n\n /**\n * 构造函数\n * @param config 配置选项\n */\n constructor(config: WebSocketConfig = {}) {\n this.config = { ...WebSocketClient.DEFAULT_CONFIG, ...config }\n this.logger = new Logger('WebSocketClient', this.config.logLevel)\n }\n\n /**\n * 更新配置\n * @param config 新的配置选项\n */\n public updateConfig(config: Partial<WebSocketConfig>): void {\n this.config = { ...this.config, ...config }\n this.logger.setLevel(this.config.logLevel ?? WebSocketClient.DEFAULT_CONFIG.logLevel)\n }\n\n /**\n * 连接到 WebSocket 服务器\n * @param url WebSocket 地址(可选,如果不传则使用配置中的 url)\n */\n public connect(url?: string): void {\n const targetUrl = url || this.config.url\n if (!targetUrl) {\n this.logger.error('[WebSocketClient] 缺少 WebSocket URL')\n return\n }\n\n this.currentUrl = targetUrl\n this.manualClose = false\n\n try {\n this.socket = new WebSocket(targetUrl)\n\n this.socket.onopen = () => {\n this.logger.info('[WebSocketClient] 连接成功')\n this.reconnectAttempts = 0\n this.startHeartbeat()\n this.onOpen()\n }\n\n this.socket.onmessage = (event: MessageEvent) => {\n this.handleIncoming(event.data)\n }\n\n this.socket.onclose = (event: CloseEvent) => {\n this.logger.info('[WebSocketClient] 连接关闭', event.code, event.reason)\n this.stopHeartbeat()\n this.onClose(event)\n\n if (!this.manualClose && this.config.autoReconnect) {\n this.scheduleReconnect()\n }\n }\n\n this.socket.onerror = (event: Event) => {\n this.logger.error('[WebSocketClient] 连接错误', event)\n this.stopHeartbeat()\n this.onError(event)\n }\n } catch (error) {\n this.logger.error('[WebSocketClient] 连接失败', error)\n if (this.config.autoReconnect && !this.manualClose) {\n this.scheduleReconnect()\n }\n }\n }\n\n /**\n * 计划重连\n */\n protected scheduleReconnect(): void {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {\n this.logger.warn('[WebSocketClient] 已达到最大重连次数')\n }\n return\n }\n\n this.reconnectAttempts += 1\n const delay = Math.min(this.config.reconnectDelay * this.reconnectAttempts, this.config.reconnectDelayMax)\n\n this.logger.debug(`[WebSocketClient] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`)\n\n this.reconnectTimer = window.setTimeout(() => {\n this.connect(this.currentUrl!)\n }, delay)\n }\n\n /**\n * 断开连接\n */\n public disconnect(): void {\n this.manualClose = true\n this.stopHeartbeat()\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n\n if (this.socket) {\n this.socket.close()\n this.socket = null\n }\n\n this.currentUrl = null\n this.reconnectAttempts = 0\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public send(data: string | object): void {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n this.logger.warn('[WebSocketClient] WebSocket 未连接,无法发送消息')\n return\n }\n\n const message = typeof data === 'string' ? data : JSON.stringify(data)\n this.socket.send(message)\n }\n\n /**\n * 处理接收的消息\n * @param data 接收到的数据\n */\n protected handleIncoming(data: string): void {\n if (!data) return\n\n let message: MessageData\n try {\n message = JSON.parse(data)\n } catch {\n this.logger.warn('[WebSocketClient] 无法解析消息', data)\n return\n }\n\n if (!message?.type) {\n return\n }\n\n // 触发匹配的回调\n const matched = this.callbackList.filter((entry) => entry.type === message.type)\n matched.forEach(({ callback }) => {\n try {\n callback(message.data, message)\n } catch (error) {\n this.logger.error('[WebSocketClient] 回调执行失败', error)\n }\n })\n\n // 调用钩子\n this.onMessage(message)\n }\n\n /**\n * 启动心跳\n */\n protected startHeartbeat(): void {\n this.stopHeartbeat()\n\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n this.heartbeatTimer = window.setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n const heartbeatData = this.config.heartbeatMessage()\n this.send(heartbeatData)\n }, this.config.heartbeatInterval)\n }\n\n /**\n * 停止心跳\n */\n protected stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public on<T = unknown>(type: string, callback: MessageCallback<T>): void\n public on<T = unknown>(entry: MessageCallbackEntry<T>): void\n public on<T = unknown>(typeOrEntry: string | MessageCallbackEntry<T>, callback?: MessageCallback<T>): void {\n const entry: MessageCallbackEntry<T> =\n typeof typeOrEntry === 'string' ? { type: typeOrEntry, callback: callback! } : typeOrEntry\n\n if (!entry.type || typeof entry.callback !== 'function') {\n this.logger.warn('[WebSocketClient] 无效的回调配置', entry)\n return\n }\n\n this.callbackList.push(entry as MessageCallbackEntry<unknown>)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选,如果不传则移除该类型的所有回调)\n */\n public off<T = unknown>(type: string, callback?: MessageCallback<T>): void {\n if (callback) {\n this.callbackList = this.callbackList.filter((entry) => !(entry.type === type && entry.callback === callback))\n } else {\n this.callbackList = this.callbackList.filter((entry) => entry.type !== type)\n }\n }\n\n /**\n * 清空所有回调\n */\n public clearCallbacks(): void {\n this.callbackList = []\n }\n\n /**\n * 获取当前连接状态\n */\n public getReadyState(): number {\n return this.socket?.readyState ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public isConnected(): boolean {\n return this.socket?.readyState === WebSocket.OPEN\n }\n\n // ========== 钩子方法(子类可以重写) ==========\n\n /**\n * 连接打开时的钩子\n */\n protected onOpen(): void {\n // 子类可以重写\n this.logger.info('[WebSocketClient] 连接打开')\n }\n\n /**\n * 连接关闭时的钩子\n */\n protected onClose(_event: CloseEvent): void {\n // 子类可以重写\n this.logger.info('[WebSocketClient] 连接打开')\n }\n\n /**\n * 连接错误时的钩子\n */\n protected onError(_event: Event): void {\n // 子类可以重写\n this.logger.error('[WebSocketClient] 连接错误', _event)\n }\n\n /**\n * 收到消息时的钩子\n */\n protected onMessage(_message: MessageData): void {\n // 子类可以重写\n this.logger.debug('[WebSocketClient] 收到消息', _message)\n }\n}\n","/**\n * SharedWorker 管理器(标签页端)\n * 负责在标签页中创建和管理 SharedWorker 连接\n */\n\nimport { Logger, LogLevel } from './logger'\nimport type { MessageCallback, MessageCallbackEntry } from './WebSocketClient'\nimport type {\n TabToWorkerMessage,\n WorkerToTabMessage,\n TabToWorkerMessageType,\n WorkerToTabMessageType,\n InitPayload,\n SendPayload,\n VisibilityPayload,\n RegisterCallbackPayload,\n UnregisterCallbackPayload,\n ServerMessagePayload,\n ErrorPayload,\n AuthConflictPayload,\n} from './types'\n\n/**\n * SharedWorker 管理器配置\n */\nexport interface SharedWorkerManagerConfig extends InitPayload {\n /** 日志级别 */\n logLevel?: LogLevel\n}\n\n/**\n * 回调条目(带 ID)\n */\ninterface CallbackEntryWithId<T = unknown> extends MessageCallbackEntry<T> {\n /** 回调ID */\n id: string\n}\n\n/**\n * SharedWorker 管理器类\n */\nexport class SharedWorkerManager {\n /** SharedWorker 实例 */\n private worker: SharedWorker | null = null\n\n /** MessagePort 实例 */\n private port: MessagePort | null = null\n\n /** 标签页ID */\n private readonly tabId: string\n\n /** 回调列表 */\n private callbacks: Map<string, CallbackEntryWithId> = new Map()\n\n /** 回调ID计数器 */\n private callbackIdCounter = 0\n\n /** 是否已连接 */\n private connected = false\n\n /** 是否已初始化可见性监听 */\n private visibilityListenerInitialized = false\n\n /** 配置 */\n private config: SharedWorkerManagerConfig\n\n /** 日志器 */\n private readonly logger: Logger\n\n /** Worker Blob URL(用于清理) */\n private workerBlobUrl: string | null = null\n\n /** 连接回调 */\n private onConnectedCallback: (() => void) | null = null\n\n /** 断开回调 */\n private onDisconnectedCallback: (() => void) | null = null\n\n /** 错误回调 */\n private onErrorCallback: ((error: ErrorPayload) => void) | null = null\n\n /** 身份冲突回调 */\n private onAuthConflictCallback: ((conflict: AuthConflictPayload) => void) | null = null\n\n /**\n * 构造函数\n */\n constructor(config: SharedWorkerManagerConfig) {\n this.config = config\n this.tabId = this.generateTabId()\n this.logger = new Logger('SharedWorkerManager', config.logLevel ?? 'warn')\n }\n\n /**\n * 生成标签页ID\n */\n private generateTabId(): string {\n return `tab_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n }\n\n /**\n * 启动 SharedWorker 连接\n */\n async start(): Promise<boolean> {\n try {\n this.logger.debug('[SharedWorkerManager] 开始启动 SharedWorker')\n\n // 创建 Worker Blob URL\n const workerScript = this.getWorkerScriptBlob()\n this.workerBlobUrl = URL.createObjectURL(workerScript)\n this.logger.debug(`[SharedWorkerManager] Worker Blob URL 创建成功: ${this.workerBlobUrl}`)\n\n // 创建 SharedWorker(使用 classic 模式,不是 module)\n this.logger.debug('[SharedWorkerManager] 正在创建 SharedWorker 实例...')\n this.worker = new SharedWorker(this.workerBlobUrl, {\n name: 'dj-common-websocket-worker',\n // 注意:不使用 type: 'module',因为 Blob URL 作为 module 有 CORS 限制\n })\n this.logger.debug('[SharedWorkerManager] ✅ SharedWorker 实例创建成功')\n\n this.port = this.worker.port\n this.port.onmessage = this.handleWorkerMessage.bind(this)\n // MessagePort 没有 onerror,错误会在 worker 中抛出\n this.port.start()\n this.logger.debug('[SharedWorkerManager] ✅ MessagePort 已启动')\n\n // 设置页面可见性监听\n this.setupVisibilityListener()\n\n // 发送初始化消息(只发送可序列化的配置项)\n const serializableConfig = {\n heartbeatInterval: this.config.config.heartbeatInterval,\n maxReconnectAttempts: this.config.config.maxReconnectAttempts,\n reconnectDelay: this.config.config.reconnectDelay,\n reconnectDelayMax: this.config.config.reconnectDelayMax,\n autoReconnect: this.config.config.autoReconnect,\n logLevel: this.config.config.logLevel,\n }\n\n this.sendToWorker(\n 'TAB_INIT' as TabToWorkerMessageType,\n {\n url: this.config.url,\n userId: this.config.userId,\n token: this.config.token,\n isVisible: !document.hidden,\n config: serializableConfig,\n sharedWorkerIdleTimeout: this.config.sharedWorkerIdleTimeout,\n } as InitPayload\n )\n\n this.logger.info('[SharedWorkerManager] SharedWorker 已启动')\n return true\n } catch (error) {\n this.logger.error('[SharedWorkerManager] 启动 SharedWorker 失败', error)\n return false\n }\n }\n\n /**\n * 停止 SharedWorker 连接\n */\n stop(): void {\n this.logger.debug('[SharedWorkerManager] 停止 SharedWorker')\n\n // 发送断开消息\n if (this.port) {\n this.sendToWorker('TAB_DISCONNECT' as TabToWorkerMessageType, {})\n }\n\n // 移除可见性监听\n this.removeVisibilityListener()\n\n // 清理资源\n if (this.port) {\n this.port.close()\n this.port = null\n }\n\n if (this.workerBlobUrl) {\n URL.revokeObjectURL(this.workerBlobUrl)\n this.workerBlobUrl = null\n }\n\n this.worker = null\n this.connected = false\n this.callbacks.clear()\n }\n\n /**\n * 发送消息到服务器\n */\n send(data: string | object): void {\n if (!this.port) {\n this.logger.warn('[SharedWorkerManager] MessagePort 未初始化,无法发送消息')\n return\n }\n\n const payload: SendPayload = { data }\n this.sendToWorker('TAB_SEND' as TabToWorkerMessageType, payload)\n }\n\n /**\n * 注册消息回调\n */\n registerCallback<T = unknown>(entry: MessageCallbackEntry<T>): string {\n const callbackId = `callback_${this.callbackIdCounter++}`\n const entryWithId: CallbackEntryWithId<T> = {\n ...entry,\n id: callbackId,\n }\n\n this.callbacks.set(callbackId, entryWithId as CallbackEntryWithId)\n\n // 通知 Worker\n if (this.port) {\n const payload: RegisterCallbackPayload = {\n type: entry.type,\n callbackId,\n }\n this.sendToWorker('TAB_REGISTER_CALLBACK' as TabToWorkerMessageType, payload)\n this.logger.debug(`[SharedWorkerManager] ✅ 已发送注册消息到 Worker: ${entry.type} (${callbackId})`)\n } else {\n this.logger.warn(`[SharedWorkerManager] ⚠️ port 未初始化,无法发送注册消息: ${entry.type}`)\n }\n\n this.logger.debug(\n `[SharedWorkerManager] 注册回调: ${entry.type} (${callbackId}), 当前回调总数: ${this.callbacks.size}`\n )\n return callbackId\n }\n\n /**\n * 取消注册消息回调\n */\n unregisterCallback(type: string, callback?: MessageCallback): void {\n if (callback) {\n // 移除特定回调\n let callbackId: string | null = null\n\n for (const [id, entry] of this.callbacks.entries()) {\n if (entry.type === type && entry.callback === callback) {\n callbackId = id\n this.callbacks.delete(id)\n break\n }\n }\n\n if (callbackId && this.port) {\n const payload: UnregisterCallbackPayload = {\n type,\n callbackId,\n }\n this.sendToWorker('TAB_UNREGISTER_CALLBACK' as TabToWorkerMessageType, payload)\n }\n\n this.logger.debug(`[SharedWorkerManager] 取消注册回调: ${type} (${callbackId})`)\n } else {\n // 移除该类型的所有回调\n const callbackIds: string[] = []\n\n for (const [id, entry] of this.callbacks.entries()) {\n if (entry.type === type) {\n callbackIds.push(id)\n this.callbacks.delete(id)\n }\n }\n\n if (this.port) {\n const payload: UnregisterCallbackPayload = { type }\n this.sendToWorker('TAB_UNREGISTER_CALLBACK' as TabToWorkerMessageType, payload)\n }\n\n this.logger.debug(`[SharedWorkerManager] 取消注册所有 ${type} 类型回调 (${callbackIds.length} 个)`)\n }\n }\n\n /**\n * 清空所有回调\n */\n clearCallbacks(): void {\n for (const entry of this.callbacks.values()) {\n if (this.port) {\n const payload: UnregisterCallbackPayload = {\n type: entry.type,\n callbackId: entry.id,\n }\n this.sendToWorker('TAB_UNREGISTER_CALLBACK' as TabToWorkerMessageType, payload)\n }\n }\n\n this.callbacks.clear()\n this.logger.debug('[SharedWorkerManager] 已清空所有回调')\n }\n\n /**\n * 是否已连接\n */\n isConnected(): boolean {\n return this.connected\n }\n\n /**\n * 设置连接回调\n */\n onConnected(callback: () => void): void {\n this.onConnectedCallback = callback\n }\n\n /**\n * 设置断开回调\n */\n onDisconnected(callback: () => void): void {\n this.onDisconnectedCallback = callback\n }\n\n /**\n * 设置错误回调\n */\n onError(callback: (error: ErrorPayload) => void): void {\n this.onErrorCallback = callback\n }\n\n /**\n * 设置身份冲突回调\n */\n onAuthConflict(callback: (conflict: AuthConflictPayload) => void): void {\n this.onAuthConflictCallback = callback\n }\n\n /**\n * 处理来自 Worker 的消息\n */\n private handleWorkerMessage(event: MessageEvent): void {\n const message = event.data as WorkerToTabMessage\n\n this.logger.debug(`[SharedWorkerManager] 📬 收到 Worker 消息, type: ${message.type}`)\n\n switch (message.type) {\n case 'WORKER_CONNECTED' as WorkerToTabMessageType:\n this.connected = true\n this.logger.info('[SharedWorkerManager] ✅ WebSocket 已连接')\n this.onConnectedCallback?.()\n break\n\n case 'WORKER_DISCONNECTED' as WorkerToTabMessageType:\n this.connected = false\n this.logger.info('[SharedWorkerManager] WebSocket 已断开')\n this.onDisconnectedCallback?.()\n break\n\n case 'WORKER_MESSAGE' as WorkerToTabMessageType:\n this.handleServerMessage(message.payload as ServerMessagePayload)\n break\n\n case 'WORKER_ERROR' as WorkerToTabMessageType:\n this.logger.error('[SharedWorkerManager] Worker 错误', message.payload)\n this.onErrorCallback?.(message.payload as ErrorPayload)\n break\n\n case 'WORKER_AUTH_CONFLICT' as WorkerToTabMessageType:\n this.logger.warn('[SharedWorkerManager] 身份冲突', message.payload)\n this.onAuthConflictCallback?.(message.payload as AuthConflictPayload)\n break\n\n case 'WORKER_PONG' as WorkerToTabMessageType:\n this.logger.debug('[SharedWorkerManager] 收到 PONG')\n break\n\n default:\n this.logger.warn('[SharedWorkerManager] 未知消息类型', message.type)\n }\n }\n\n /**\n * 处理服务器消息\n */\n private handleServerMessage(payload: ServerMessagePayload): void {\n const { message } = payload\n\n this.logger.debug(`[SharedWorkerManager] 📨 收到服务器消息, type: ${message.type}`)\n this.logger.debug(\n `[SharedWorkerManager] 当前注册的回调类型:`,\n Array.from(this.callbacks.values()).map((e) => e.type)\n )\n\n // 触发匹配的回调\n let matchedCount = 0\n for (const entry of this.callbacks.values()) {\n if (entry.type === message.type) {\n matchedCount++\n this.logger.debug(`[SharedWorkerManager] ✅ 匹配到回调 ${entry.type} (${entry.id}),准备执行`)\n try {\n entry.callback(message.data, message)\n this.logger.debug(`[SharedWorkerManager] ✅ 回调执行成功 ${entry.type} (${entry.id})`)\n } catch (error) {\n this.logger.error('[SharedWorkerManager] ❌ 回调执行失败', error)\n }\n }\n }\n\n if (matchedCount === 0) {\n this.logger.warn(`[SharedWorkerManager] ⚠️ 没有匹配的回调: ${message.type}`)\n }\n }\n\n /**\n * 发送消息到 Worker\n */\n private sendToWorker(type: TabToWorkerMessageType, payload: unknown): void {\n if (!this.port) {\n this.logger.warn('[SharedWorkerManager] MessagePort 未初始化,无法发送消息')\n return\n }\n\n const message: TabToWorkerMessage = {\n type,\n payload,\n tabId: this.tabId,\n timestamp: Date.now(),\n }\n\n try {\n this.port.postMessage(message)\n } catch (error) {\n this.logger.error('[SharedWorkerManager] 发送消息到 Worker 失败', error)\n }\n }\n\n /**\n * 设置页面可见性监听\n */\n private setupVisibilityListener(): void {\n if (typeof document === 'undefined' || this.visibilityListenerInitialized) {\n return\n }\n\n document.addEventListener('visibilitychange', this.handleVisibilityChange)\n this.visibilityListenerInitialized = true\n this.logger.debug('[SharedWorkerManager] 已设置页面可见性监听')\n }\n\n /**\n * 移除页面可见性监听\n */\n private removeVisibilityListener(): void {\n if (typeof document === 'undefined' || !this.visibilityListenerInitialized) {\n return\n }\n\n document.removeEventListener('visibilitychange', this.handleVisibilityChange)\n this.visibilityListenerInitialized = false\n this.logger.debug('[SharedWorkerManager] 已移除页面可见性监听')\n }\n\n /**\n * 处理页面可见性变化\n */\n private handleVisibilityChange = (): void => {\n const isVisible = !document.hidden\n this.logger.debug(`[SharedWorkerManager] 页面可见性变化: ${isVisible}`)\n\n if (this.port) {\n const payload: VisibilityPayload = { isVisible }\n this.sendToWorker('TAB_VISIBILITY' as TabToWorkerMessageType, payload)\n }\n }\n\n /**\n * 获取 Worker 脚本 Blob\n */\n private getWorkerScriptBlob(): Blob {\n try {\n const workerCode = this.getWorkerScriptContent()\n this.logger.debug(`[SharedWorkerManager] 正在创建 Worker Blob, 代码长度: ${workerCode.length}`)\n\n const blob = new Blob([workerCode], { type: 'application/javascript' })\n this.logger.debug(`[SharedWorkerManager] ✅ Worker Blob 创建成功, size: ${blob.size}`)\n\n return blob\n } catch (error) {\n this.logger.error('[SharedWorkerManager] ❌ 创建 Worker Blob 失败:', error)\n throw error\n }\n }\n\n /**\n * 获取 Worker 脚本内容\n * 这里使用占位符,在构建时会被替换为实际的 Worker 代码\n */\n private getWorkerScriptContent(): string {\n // 占位符,构建时会被替换\n const content: string = '__WORKER_SCRIPT_CONTENT__'\n\n // 调试:检查内容是否被替换\n if (content === '__WORKER_SCRIPT_CONTENT__') {\n this.logger.error('[SharedWorkerManager] ❌ Worker 代码未被内联!构建配置有问题')\n throw new Error('Worker script not inlined during build')\n }\n\n this.logger.debug(`[SharedWorkerManager] Worker 脚本长度: ${content.length} 字符`)\n return content\n }\n}\n","import { WebSocketClient, WebSocketConfig, MessageCallbackEntry, MessageCallback } from './WebSocketClient'\nimport { Logger, LogLevel } from './logger'\nimport { SharedWorkerManager } from './SharedWorkerManager'\nimport type { ConnectionMode } from './types'\n\n/**\n * MessageSocket 配置选项\n */\nexport interface MessageSocketConfig extends WebSocketConfig {\n /** WebSocket 服务器地址,默认 '' */\n url?: string\n /** 消息回调列表 */\n callbacks?: MessageCallbackEntry[]\n /** 日志级别 */\n logLevel?: LogLevel\n /** 是否启用页面可见性管理(标签页切换时自动断开/重连),默认 false */\n enableVisibilityManagement?: boolean\n /** 连接模式,默认 'auto' */\n connectionMode?: ConnectionMode\n /** SharedWorker 空闲超时时间(毫秒),默认 30000 */\n sharedWorkerIdleTimeout?: number\n}\n\n/**\n * MessageSocket 启动选项\n */\nexport interface MessageSocketStartOptions {\n /** 用户ID(必需) */\n userId: string\n /** 认证令牌(必需) */\n token: string\n}\n\n/**\n * 消息 Socket 类\n * 基于 WebSocketClient,提供用户认证和消息管理功能\n * 用于获取用户未读消息数量等业务场景\n *\n * @example\n * ```typescript\n * // 启动连接\n * MessageSocket.start({\n * userId: '1234567890',\n * token: 'your-token',\n * })\n *\n * // 停止连接\n * MessageSocket.stop()\n *\n * // 设置配置\n * MessageSocket.setConfig({\n * baseUrl: 'ws://your-server.com',\n * path: '/your/path',\n * })\n *\n * // 初始化时设置回调\n * MessageSocket.setCallbacks([\n * {\n * type: 'UNREAD_COUNT',\n * callback: (payload) => {\n * console.log('未读消息数:', payload)\n * }\n * }\n * ])\n * // 动态注册回调\n * MessageSocket.registerCallbacks({\n * type: 'NEW_MESSAGE',\n * callback: (payload) => {\n * console.log('新消息:', payload)\n * }\n * })\n * ```\n * // logLevel 设置日志级别,默认 'warn',可选 'debug', 'info', 'warn', 'error', 'silent'\n *\n */\nexport class MessageSocket {\n /** 默认配置 */\n private static readonly DEFAULT_CONFIG: Required<MessageSocketConfig> = {\n url: '',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n callbacks: [],\n heartbeatMessage: () => ({\n type: 'PING',\n timestamp: Date.now(),\n }),\n logLevel: 'warn',\n enableVisibilityManagement: false,\n connectionMode: 'auto',\n sharedWorkerIdleTimeout: 30000,\n }\n\n /** WebSocket 客户端实例 */\n private static client: WebSocketClient | null = null\n\n /** SharedWorker 管理器实例 */\n private static workerManager: SharedWorkerManager | null = null\n\n /** 当前连接模式 */\n private static currentMode: 'sharedWorker' | 'visibility' | 'normal' = 'normal'\n\n private static readonly logger = new Logger('MessageSocket', MessageSocket.DEFAULT_CONFIG.logLevel)\n\n private static updateLoggerLevel(): void {\n MessageSocket.logger.setLevel(MessageSocket.config.logLevel ?? MessageSocket.DEFAULT_CONFIG.logLevel)\n }\n\n /** 当前用户ID */\n private static currentUserId: string | null = null\n\n /** 当前token */\n private static currentToken: string | null = null\n\n /** 当前配置 */\n private static config: MessageSocketConfig = { ...MessageSocket.DEFAULT_CONFIG }\n\n /** 是否已初始化页面可见性监听 */\n private static visibilityListenerInitialized = false\n\n /**\n * 页面可见性变化处理器\n * 当标签页不可见时断开连接,可见时重新连接\n */\n private static handleVisibilityChange = (): void => {\n if (typeof document === 'undefined') return\n\n const isVisible = !document.hidden\n\n if (!isVisible) {\n // 页面不可见时断开连接,避免多标签页重复连接\n MessageSocket.logger.info('[MessageSocket] 页面不可见,断开连接')\n if (MessageSocket.client) {\n MessageSocket.client.disconnect()\n }\n } else {\n // 页面可见时重新连接\n if (MessageSocket.currentUserId && MessageSocket.currentToken) {\n MessageSocket.logger.info('[MessageSocket] 页面可见,尝试重新连接')\n // 检查是否已经有活跃连接\n if (!MessageSocket.client || !MessageSocket.client.isConnected()) {\n MessageSocket.start({\n userId: MessageSocket.currentUserId,\n token: MessageSocket.currentToken,\n })\n }\n }\n }\n }\n\n /**\n * 初始化页面可见性监听\n */\n private static initVisibilityListener(): void {\n if (typeof document === 'undefined' || MessageSocket.visibilityListenerInitialized) {\n return\n }\n\n document.addEventListener('visibilitychange', MessageSocket.handleVisibilityChange)\n MessageSocket.visibilityListenerInitialized = true\n MessageSocket.logger.debug('[MessageSocket] 已初始化页面可见性监听')\n }\n\n /**\n * 移除页面可见性监听\n */\n private static removeVisibilityListener(): void {\n if (typeof document === 'undefined' || !MessageSocket.visibilityListenerInitialized) {\n return\n }\n\n document.removeEventListener('visibilitychange', MessageSocket.handleVisibilityChange)\n MessageSocket.visibilityListenerInitialized = false\n MessageSocket.logger.debug('[MessageSocket] 已移除页面可见性监听')\n }\n\n /**\n * 检测 SharedWorker 支持\n */\n private static detectSharedWorkerSupport(): boolean {\n return typeof SharedWorker !== 'undefined' && typeof Blob !== 'undefined'\n }\n\n /**\n * 检测 Visibility API 支持\n */\n private static detectVisibilitySupport(): boolean {\n return typeof document !== 'undefined' && typeof document.hidden !== 'undefined'\n }\n\n /**\n * 决定连接模式\n */\n private static determineConnectionMode(): 'sharedWorker' | 'visibility' | 'normal' {\n const mode = MessageSocket.config.connectionMode || 'auto'\n\n if (mode === 'auto') {\n // 自动选择最佳模式\n if (this.detectSharedWorkerSupport()) {\n MessageSocket.logger.info('[MessageSocket] 自动选择 SharedWorker 模式')\n return 'sharedWorker'\n }\n if (this.detectVisibilitySupport() && MessageSocket.config.enableVisibilityManagement) {\n MessageSocket.logger.info('[MessageSocket] 自动选择 Visibility 模式')\n return 'visibility'\n }\n MessageSocket.logger.info('[MessageSocket] 自动选择 Normal 模式')\n return 'normal'\n }\n\n if (mode === 'sharedWorker') {\n if (this.detectSharedWorkerSupport()) {\n MessageSocket.logger.info('[MessageSocket] 使用 SharedWorker 模式')\n return 'sharedWorker'\n }\n // 降级\n MessageSocket.logger.warn('[MessageSocket] 浏览器不支持 SharedWorker,降级到 Visibility 模式')\n if (this.detectVisibilitySupport() && MessageSocket.config.enableVisibilityManagement) {\n return 'visibility'\n }\n MessageSocket.logger.warn('[MessageSocket] 降级到 Normal 模式')\n return 'normal'\n }\n\n if (mode === 'visibility') {\n if (this.detectVisibilitySupport()) {\n MessageSocket.logger.info('[MessageSocket] 使用 Visibility 模式')\n return 'visibility'\n }\n MessageSocket.logger.warn('[MessageSocket] 浏览器不支持 Visibility API,降级到 Normal 模式')\n return 'normal'\n }\n\n // normal 模式\n MessageSocket.logger.info('[MessageSocket] 使用 Normal 模式')\n return 'normal'\n }\n\n /**\n * 配置 MessageSocket\n * @param config 配置选项\n */\n public static configure(config: Partial<MessageSocketConfig>): void {\n MessageSocket.config = { ...MessageSocket.config, ...config }\n }\n\n /**\n * 设置配置\n * @param config 配置选项\n * @returns MessageSocket\n */\n public static setConfig(config: Partial<MessageSocketConfig>): typeof MessageSocket {\n MessageSocket.config = { ...MessageSocket.config, ...config }\n MessageSocket.updateLoggerLevel()\n if (MessageSocket.config.callbacks && MessageSocket.config.callbacks.length > 0) {\n MessageSocket.setCallbacks(MessageSocket.config.callbacks)\n }\n return MessageSocket\n }\n\n /**\n * 设置回调\n * @param callbacks 回调列表\n * @returns MessageSocket\n */\n public static setCallbacks(callbacks: MessageCallbackEntry[]): typeof MessageSocket {\n if (!callbacks || callbacks.length === 0) {\n this.logger.warn('[MessageSocket] 回调列表为空,无法设置回调')\n return MessageSocket\n }\n\n MessageSocket.config.callbacks = callbacks\n return MessageSocket\n }\n\n /**\n * 启动连接\n * @param options 启动选项\n * 回调在开始的时候注册,这种能\n */\n public static start(options: MessageSocketStartOptions): void {\n if (!MessageSocket.config.url) {\n this.logger.error('[MessageSocket] 缺少配置 url, 请先调用 setConfig 设置配置!')\n return\n }\n\n const { userId, token } = options\n\n if (!userId || !token) {\n this.logger.error('[MessageSocket] 缺少 userId 或 token,无法启动')\n return\n }\n\n this.logger.info('[MessageSocket] 开始连接', userId)\n\n // 决定连接模式\n MessageSocket.currentMode = MessageSocket.determineConnectionMode()\n\n // 根据模式启动\n if (MessageSocket.currentMode === 'sharedWorker') {\n MessageSocket.startWithSharedWorker(options)\n } else if (MessageSocket.currentMode === 'visibility') {\n MessageSocket.startWithVisibility(options)\n } else {\n MessageSocket.startWithNormalMode(options)\n }\n }\n\n /**\n * 使用 SharedWorker 模式启动\n */\n private static async startWithSharedWorker(options: MessageSocketStartOptions): Promise<void> {\n const { userId, token } = options\n\n // 停止旧连接\n MessageSocket.stop()\n\n // 保存当前用户信息\n MessageSocket.currentUserId = userId\n MessageSocket.currentToken = token\n\n // 创建 SharedWorker 管理器\n MessageSocket.workerManager = new SharedWorkerManager({\n url: MessageSocket.config.url!,\n userId,\n token,\n isVisible: typeof document !== 'undefined' ? !document.hidden : true,\n config: MessageSocket.config,\n sharedWorkerIdleTimeout: MessageSocket.config.sharedWorkerIdleTimeout,\n logLevel: MessageSocket.config.logLevel,\n })\n\n // 设置错误回调(降级)\n MessageSocket.workerManager.onError((error) => {\n MessageSocket.logger.warn('[MessageSocket] SharedWorker 失败,降级到 Visibility 模式', error)\n MessageSocket.currentMode = 'visibility'\n MessageSocket.workerManager = null\n MessageSocket.startWithVisibility(options)\n })\n\n // 启动 Worker(必须先启动,创建 port 后才能注册回调)\n const success = await MessageSocket.workerManager.start()\n\n if (!success) {\n // 降级\n MessageSocket.logger.warn('[MessageSocket] SharedWorker 启动失败,降级到 Visibility 模式')\n MessageSocket.currentMode = 'visibility'\n MessageSocket.workerManager = null\n MessageSocket.startWithVisibility(options)\n return\n }\n\n // 注册已有的回调(必须在 start() 之后,此时 port 已创建)\n if (MessageSocket.config.callbacks && MessageSocket.config.callbacks.length > 0) {\n MessageSocket.config.callbacks.forEach((entry) => {\n MessageSocket.workerManager!.registerCallback(entry)\n })\n }\n }\n\n /**\n * 使用 Visibility 模式启动\n */\n private static startWithVisibility(options: MessageSocketStartOptions): void {\n const { userId, token } = options\n\n // 检查是否可以复用现有连接\n const shouldReuse =\n MessageSocket.client &&\n MessageSocket.client.isConnected() &&\n MessageSocket.currentUserId === userId &&\n MessageSocket.currentToken === token\n\n if (shouldReuse) {\n this.logger.debug('[MessageSocket] 复用现有连接')\n return\n }\n\n // 停止旧连接\n MessageSocket.stop()\n\n // 保存当前用户信息\n MessageSocket.currentUserId = userId\n MessageSocket.currentToken = token\n\n // 构建 WebSocket URL\n const { url, ...clientConfig } = MessageSocket.config\n const targetUrl = `${url}/${userId}?token=${encodeURIComponent(token)}`\n\n // 创建新的 WebSocket 客户端\n MessageSocket.client = new WebSocketClient({\n ...clientConfig,\n url: targetUrl,\n })\n\n // 注册回调(直接注册到 client,不经过 registerCallbacks)\n if (MessageSocket.config.callbacks && MessageSocket.config.callbacks.length > 0) {\n MessageSocket.config.callbacks.forEach((entry) => MessageSocket.client!.on(entry))\n }\n\n // 初始化页面可见性监听\n MessageSocket.initVisibilityListener()\n\n // 连接\n MessageSocket.client.connect()\n }\n\n /**\n * 使用 Normal 模式启动\n */\n private static startWithNormalMode(options: MessageSocketStartOptions): void {\n const { userId, token } = options\n\n // 检查是否可以复用现有连接\n const shouldReuse =\n MessageSocket.client &&\n MessageSocket.client.isConnected() &&\n MessageSocket.currentUserId === userId &&\n MessageSocket.currentToken === token\n\n if (shouldReuse) {\n this.logger.debug('[MessageSocket] 复用现有连接')\n return\n }\n\n // 停止旧连接\n MessageSocket.stop()\n\n // 保存当前用户信息\n MessageSocket.currentUserId = userId\n MessageSocket.currentToken = token\n\n // 构建 WebSocket URL\n const { url, ...clientConfig } = MessageSocket.config\n const targetUrl = `${url}/${userId}?token=${encodeURIComponent(token)}`\n\n // 创建新的 WebSocket 客户端\n MessageSocket.client = new WebSocketClient({\n ...clientConfig,\n url: targetUrl,\n })\n\n // 注册回调(直接注册到 client,不经过 registerCallbacks)\n if (MessageSocket.config.callbacks && MessageSocket.config.callbacks.length > 0) {\n MessageSocket.config.callbacks.forEach((entry) => MessageSocket.client!.on(entry))\n }\n\n // 连接\n MessageSocket.client.connect()\n }\n\n /**\n * 停止连接\n */\n public static stop(): void {\n if (MessageSocket.client) {\n MessageSocket.client.disconnect()\n MessageSocket.client.clearCallbacks()\n MessageSocket.client = null\n }\n\n if (MessageSocket.workerManager) {\n MessageSocket.workerManager.stop()\n MessageSocket.workerManager = null\n }\n\n // 清理页面可见性监听\n MessageSocket.removeVisibilityListener()\n\n MessageSocket.currentUserId = null\n MessageSocket.currentToken = null\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public static registerCallbacks<T = unknown>(entry: MessageCallbackEntry<T>): void {\n if (!entry) {\n this.logger.warn('[MessageSocket] 注册回调失败,缺少 entry', entry)\n return\n }\n\n if (typeof entry !== 'object') {\n this.logger.warn('[MessageSocket] 注册回调失败,entry 不是对象', entry)\n return\n }\n\n if (typeof entry.callback !== 'function') {\n this.logger.warn('[MessageSocket] 注册回调失败,callback 不是函数', entry)\n return\n }\n\n if (typeof entry.type !== 'string') {\n this.logger.warn('[MessageSocket] 注册回调失败,type 不是字符串', entry)\n return\n }\n\n // 根据当前模式注册回调\n if (MessageSocket.currentMode === 'sharedWorker' && MessageSocket.workerManager) {\n MessageSocket.workerManager.registerCallback(entry)\n } else if (MessageSocket.client) {\n MessageSocket.client.on(entry)\n } else {\n this.logger.warn('[MessageSocket] 无可用连接,无法注册回调')\n }\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选)\n */\n public static unregisterCallbacks<T = unknown>(type: string, callback?: (data: T, message?: unknown) => void): void {\n // 根据当前模式取消注册\n if (MessageSocket.currentMode === 'sharedWorker' && MessageSocket.workerManager) {\n MessageSocket.workerManager.unregisterCallback(type, callback as MessageCallback)\n } else if (MessageSocket.client) {\n MessageSocket.client.off(type, callback)\n }\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public static send(data: string | object): void {\n // 根据当前模式发送\n if (MessageSocket.currentMode === 'sharedWorker' && MessageSocket.workerManager) {\n MessageSocket.workerManager.send(data)\n } else if (MessageSocket.client) {\n MessageSocket.client.send(data)\n } else {\n this.logger.warn('[MessageSocket] 无可用连接,无法发送消息')\n }\n }\n\n /**\n * 获取连接状态\n */\n public static getReadyState(): number {\n return MessageSocket.client?.getReadyState() ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public static isConnected(): boolean {\n if (MessageSocket.currentMode === 'sharedWorker' && MessageSocket.workerManager) {\n return MessageSocket.workerManager.isConnected()\n }\n return MessageSocket.client?.isConnected() ?? false\n }\n\n /**\n * 获取当前连接模式\n */\n public static getConnectionMode(): 'sharedWorker' | 'visibility' | 'normal' {\n return MessageSocket.currentMode\n }\n\n /**\n * 获取当前用户ID\n */\n public static getCurrentUserId(): string | null {\n return MessageSocket.currentUserId\n }\n\n /**\n * 获取当前token\n */\n public static getCurrentToken(): string | null {\n return MessageSocket.currentToken\n }\n}\n"],"names":["LOG_LEVEL_PRIORITY","debug","info","warn","error","silent","Logger","constructor","name","level","this","setLevel","getLevel","values","logAtLevel","console","shouldLog","writer","WebSocketClient","config","socket","heartbeatTimer","reconnectTimer","callbackList","currentUrl","reconnectAttempts","manualClose","DEFAULT_CONFIG","logger","logLevel","updateConfig","connect","url","targetUrl","WebSocket","onopen","startHeartbeat","onOpen","onmessage","event","handleIncoming","data","onclose","code","reason","stopHeartbeat","onClose","autoReconnect","scheduleReconnect","onerror","onError","maxReconnectAttempts","delay","Math","min","reconnectDelay","reconnectDelayMax","window","setTimeout","disconnect","clearTimeout","close","send","readyState","OPEN","message","JSON","stringify","parse","type","filter","entry","forEach","callback","onMessage","setInterval","heartbeatData","heartbeatMessage","heartbeatInterval","clearInterval","on","typeOrEntry","push","off","clearCallbacks","getReadyState","CLOSED","isConnected","_event","_message","timestamp","Date","now","SharedWorkerManager","worker","port","callbacks","Map","callbackIdCounter","connected","visibilityListenerInitialized","workerBlobUrl","onConnectedCallback","onDisconnectedCallback","onErrorCallback","onAuthConflictCallback","handleVisibilityChange","isVisible","document","hidden","payload","sendToWorker","tabId","generateTabId","random","toString","substring","start","workerScript","getWorkerScriptBlob","URL","createObjectURL","SharedWorker","handleWorkerMessage","bind","setupVisibilityListener","serializableConfig","userId","token","sharedWorkerIdleTimeout","stop","removeVisibilityListener","revokeObjectURL","clear","registerCallback","callbackId","entryWithId","id","set","size","unregisterCallback","entries","delete","callbackIds","length","onConnected","onDisconnected","onAuthConflict","handleServerMessage","Array","from","map","e","matchedCount","postMessage","addEventListener","removeEventListener","workerCode","getWorkerScriptContent","blob","Blob","content","MessageSocket","updateLoggerLevel","initVisibilityListener","detectSharedWorkerSupport","detectVisibilitySupport","determineConnectionMode","mode","connectionMode","enableVisibilityManagement","configure","setConfig","setCallbacks","options","currentMode","startWithSharedWorker","startWithVisibility","startWithNormalMode","currentUserId","currentToken","workerManager","client","clientConfig","encodeURIComponent","registerCallbacks","unregisterCallbacks","getConnectionMode","getCurrentUserId","getCurrentToken"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/logger.ts","../src/WebSocketClient.ts","../src/SharedWorkerManager.ts","../src/MessageSocket.ts"],"sourcesContent":["export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'\n\nexport const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n silent: 50,\n}\n\nexport class Logger {\n private level: LogLevel\n\n constructor(\n private readonly name: string,\n level: LogLevel = 'warn'\n ) {\n this.level = level\n }\n\n setLevel(level: LogLevel): void {\n this.level = level\n }\n\n getLevel(): LogLevel {\n return this.level\n }\n\n debug(...values: unknown[]): void {\n this.logAtLevel('debug', console.debug, values)\n }\n\n info(...values: unknown[]): void {\n this.logAtLevel('info', console.info, values)\n }\n\n warn(...values: unknown[]): void {\n this.logAtLevel('warn', console.warn, values)\n }\n\n error(...values: unknown[]): void {\n this.logAtLevel('error', console.error, values)\n }\n\n private shouldLog(level: LogLevel): boolean {\n if (level === 'silent') {\n return false\n }\n return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.level]\n }\n\n private logAtLevel(level: LogLevel, writer: (...args: unknown[]) => void, values: unknown[]): void {\n if (!this.shouldLog(level)) {\n return\n }\n writer(`[${this.name}]`, ...values)\n }\n}\n","import { Logger, LogLevel } from './logger'\n\n/**\n * WebSocket 配置选项\n */\nexport interface WebSocketConfig {\n /** WebSocket 服务器地址(可选,可以在 connect 时指定) */\n url?: string\n /** 心跳间隔(毫秒),默认 25000 */\n heartbeatInterval?: number\n /** 最大重连次数,默认 10 */\n maxReconnectAttempts?: number\n /** 重连延迟(毫秒),默认 3000 */\n reconnectDelay?: number\n /** 最大重连延迟(毫秒),默认 10000 */\n reconnectDelayMax?: number\n /** 心跳消息生成器 */\n heartbeatMessage?: () => string | object\n /** 是否自动重连,默认 true */\n autoReconnect?: boolean\n /** 日志输出等级 */\n logLevel?: LogLevel\n /** 是否启用网络状态监听(网络恢复时自动重连),默认 true */\n enableNetworkListener?: boolean\n}\n\n/**\n * 消息数据结构\n */\nexport interface MessageData<T = unknown> {\n /** 消息类型 */\n type: string\n /** 消息数据 */\n data: T\n /** 元数据 */\n meta?: Record<string, unknown>\n /** 时间戳 */\n timestamp?: number\n}\n\n/**\n * 消息回调函数\n */\nexport type MessageCallback<T = unknown> = (data: T, message?: MessageData<T>) => void\n\n/**\n * 消息回调配置\n */\nexport interface MessageCallbackEntry<T = unknown> {\n /** 消息类型 */\n type: string\n /** 回调函数 */\n callback: MessageCallback<T>\n}\n\n/**\n * WebSocket 基础封装类\n * 提供连接管理、心跳、自动重连、消息回调等基础功能\n */\nexport class WebSocketClient {\n /** WebSocket 连接实例 */\n protected socket: WebSocket | null = null\n\n /** 心跳定时器 */\n protected heartbeatTimer: number | null = null\n\n /** 重连定时器 */\n protected reconnectTimer: number | null = null\n\n /** 消息回调列表 */\n protected callbackList: MessageCallbackEntry<unknown>[] = []\n\n /** 当前连接的 URL */\n protected currentUrl: string | null = null\n\n /** 配置选项 */\n protected config: Required<WebSocketConfig>\n\n /** 重连次数 */\n protected reconnectAttempts = 0\n\n /** 是否手动关闭 */\n protected manualClose = false\n\n /** 是否已初始化网络监听 */\n protected networkListenerInitialized = false\n\n /** 日志器 */\n protected readonly logger: Logger\n\n /**\n * 默认配置\n */\n protected static readonly DEFAULT_CONFIG: Required<WebSocketConfig> = {\n url: '',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n heartbeatMessage: () => ({\n type: 'PING',\n timestamp: Date.now(),\n }),\n logLevel: 'warn',\n enableNetworkListener: true,\n }\n\n /**\n * 构造函数\n * @param config 配置选项\n */\n constructor(config: WebSocketConfig = {}) {\n this.config = { ...WebSocketClient.DEFAULT_CONFIG, ...config }\n this.logger = new Logger('WebSocketClient', this.config.logLevel)\n }\n\n /**\n * 更新配置\n * @param config 新的配置选项\n */\n public updateConfig(config: Partial<WebSocketConfig>): void {\n this.config = { ...this.config, ...config }\n this.logger.setLevel(this.config.logLevel ?? WebSocketClient.DEFAULT_CONFIG.logLevel)\n }\n\n /**\n * 连接到 WebSocket 服务器\n * @param url WebSocket 地址(可选,如果不传则使用配置中的 url)\n */\n public connect(url?: string): void {\n const targetUrl = url || this.config.url\n if (!targetUrl) {\n this.logger.error('[WebSocketClient] 缺少 WebSocket URL')\n return\n }\n\n this.currentUrl = targetUrl\n this.manualClose = false\n\n // 初始化网络状态监听\n this.initNetworkListener()\n\n try {\n this.socket = new WebSocket(targetUrl)\n\n this.socket.onopen = () => {\n this.logger.info('[WebSocketClient] 连接成功')\n this.reconnectAttempts = 0\n this.startHeartbeat()\n this.onOpen()\n }\n\n this.socket.onmessage = (event: MessageEvent) => {\n this.handleIncoming(event.data)\n }\n\n this.socket.onclose = (event: CloseEvent) => {\n this.logger.info('[WebSocketClient] 连接关闭', event.code, event.reason)\n this.stopHeartbeat()\n this.onClose(event)\n\n if (!this.manualClose && this.config.autoReconnect) {\n this.scheduleReconnect()\n }\n }\n\n this.socket.onerror = (event: Event) => {\n this.logger.error('[WebSocketClient] 连接错误', event)\n this.stopHeartbeat()\n this.onError(event)\n }\n } catch (error) {\n this.logger.error('[WebSocketClient] 连接失败', error)\n if (this.config.autoReconnect && !this.manualClose) {\n this.scheduleReconnect()\n }\n }\n }\n\n /**\n * 计划重连\n */\n protected scheduleReconnect(): void {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {\n this.logger.warn('[WebSocketClient] 已达到最大重连次数')\n }\n return\n }\n\n this.reconnectAttempts += 1\n const delay = Math.min(this.config.reconnectDelay * this.reconnectAttempts, this.config.reconnectDelayMax)\n\n this.logger.debug(`[WebSocketClient] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`)\n\n this.reconnectTimer = window.setTimeout(() => {\n this.connect(this.currentUrl!)\n }, delay)\n }\n\n /**\n * 断开连接\n */\n public disconnect(): void {\n this.manualClose = true\n this.stopHeartbeat()\n this.removeNetworkListener()\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n\n if (this.socket) {\n this.socket.close()\n this.socket = null\n }\n\n this.currentUrl = null\n this.reconnectAttempts = 0\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public send(data: string | object): void {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n this.logger.warn('[WebSocketClient] WebSocket 未连接,无法发送消息')\n return\n }\n\n const message = typeof data === 'string' ? data : JSON.stringify(data)\n this.socket.send(message)\n }\n\n /**\n * 处理接收的消息\n * @param data 接收到的数据\n */\n protected handleIncoming(data: string): void {\n if (!data) return\n\n let message: MessageData\n try {\n message = JSON.parse(data)\n } catch {\n this.logger.warn('[WebSocketClient] 无法解析消息', data)\n return\n }\n\n if (!message?.type) {\n return\n }\n\n // 触发匹配的回调\n const matched = this.callbackList.filter((entry) => entry.type === message.type)\n matched.forEach(({ callback }) => {\n try {\n callback(message.data, message)\n } catch (error) {\n this.logger.error('[WebSocketClient] 回调执行失败', error)\n }\n })\n\n // 调用钩子\n this.onMessage(message)\n }\n\n /**\n * 启动心跳\n */\n protected startHeartbeat(): void {\n this.stopHeartbeat()\n\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n this.heartbeatTimer = window.setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n const heartbeatData = this.config.heartbeatMessage()\n this.send(heartbeatData)\n }, this.config.heartbeatInterval)\n }\n\n /**\n * 停止心跳\n */\n protected stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public on<T = unknown>(type: string, callback: MessageCallback<T>): void\n public on<T = unknown>(entry: MessageCallbackEntry<T>): void\n public on<T = unknown>(typeOrEntry: string | MessageCallbackEntry<T>, callback?: MessageCallback<T>): void {\n const entry: MessageCallbackEntry<T> =\n typeof typeOrEntry === 'string' ? { type: typeOrEntry, callback: callback! } : typeOrEntry\n\n if (!entry.type || typeof entry.callback !== 'function') {\n this.logger.warn('[WebSocketClient] 无效的回调配置', entry)\n return\n }\n\n this.callbackList.push(entry as MessageCallbackEntry<unknown>)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选,如果不传则移除该类型的所有回调)\n */\n public off<T = unknown>(type: string, callback?: MessageCallback<T>): void {\n if (callback) {\n this.callbackList = this.callbackList.filter((entry) => !(entry.type === type && entry.callback === callback))\n } else {\n this.callbackList = this.callbackList.filter((entry) => entry.type !== type)\n }\n }\n\n /**\n * 清空所有回调\n */\n public clearCallbacks(): void {\n this.callbackList = []\n }\n\n /**\n * 获取当前连接状态\n */\n public getReadyState(): number {\n return this.socket?.readyState ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public isConnected(): boolean {\n return this.socket?.readyState === WebSocket.OPEN\n }\n\n // ========== 钩子方法(子类可以重写) ==========\n\n /**\n * 连接打开时的钩子\n */\n protected onOpen(): void {\n // 子类可以重写\n this.logger.info('[WebSocketClient] 连接打开')\n }\n\n /**\n * 连接关闭时的钩子\n */\n protected onClose(_event: CloseEvent): void {\n // 子类可以重写\n this.logger.info('[WebSocketClient] 连接打开')\n }\n\n /**\n * 连接错误时的钩子\n */\n protected onError(_event: Event): void {\n // 子类可以重写\n this.logger.error('[WebSocketClient] 连接错误', _event)\n }\n\n /**\n * 收到消息时的钩子\n */\n protected onMessage(_message: MessageData): void {\n // 子类可以重写\n this.logger.debug('[WebSocketClient] 收到消息', _message)\n }\n\n // ========== 网络状态监听 ==========\n\n /**\n * 网络恢复时的处理函数\n */\n protected handleOnline = (): void => {\n this.logger.info('[WebSocketClient] 网络已恢复')\n\n // 如果是手动关闭的,不自动重连\n if (this.manualClose) {\n return\n }\n\n // 如果已连接,无需重连\n if (this.isConnected()) {\n return\n }\n\n // 重置重连计数,立即重连\n this.reconnectAttempts = 0\n\n // 清除可能存在的重连定时器\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n\n // 立即尝试重连\n if (this.currentUrl && this.config.autoReconnect) {\n this.logger.info('[WebSocketClient] 网络恢复,立即尝试重连')\n this.connect(this.currentUrl)\n }\n }\n\n /**\n * 网络断开时的处理函数\n */\n protected handleOffline = (): void => {\n this.logger.info('[WebSocketClient] 网络已断开')\n\n // 清除重连定时器,避免在无网络时浪费重连尝试\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n }\n\n /**\n * 初始化网络状态监听\n */\n protected initNetworkListener(): void {\n if (typeof window === 'undefined' || this.networkListenerInitialized) {\n return\n }\n\n if (!this.config.enableNetworkListener) {\n return\n }\n\n window.addEventListener('online', this.handleOnline)\n window.addEventListener('offline', this.handleOffline)\n this.networkListenerInitialized = true\n this.logger.debug('[WebSocketClient] 已初始化网络状态监听')\n }\n\n /**\n * 移除网络状态监听\n */\n protected removeNetworkListener(): void {\n if (typeof window === 'undefined' || !this.networkListenerInitialized) {\n return\n }\n\n window.removeEventListener('online', this.handleOnline)\n window.removeEventListener('offline', this.handleOffline)\n this.networkListenerInitialized = false\n this.logger.debug('[WebSocketClient] 已移除网络状态监听')\n }\n}\n","/**\n * SharedWorker 管理器(标签页端)\n * 负责在标签页中创建和管理 SharedWorker 连接\n */\n\nimport { Logger, LogLevel } from './logger'\nimport type { MessageCallback, MessageCallbackEntry } from './WebSocketClient'\nimport type {\n TabToWorkerMessage,\n WorkerToTabMessage,\n TabToWorkerMessageType,\n WorkerToTabMessageType,\n InitPayload,\n SendPayload,\n VisibilityPayload,\n RegisterCallbackPayload,\n UnregisterCallbackPayload,\n ServerMessagePayload,\n ErrorPayload,\n AuthConflictPayload,\n} from './types'\n\n/**\n * SharedWorker 管理器配置\n */\nexport interface SharedWorkerManagerConfig extends InitPayload {\n /** 日志级别 */\n logLevel?: LogLevel\n /** 启动时强制新建 SharedWorker(会生成新的 worker 会话名,并尝试关闭旧 worker) */\n forceNewWorkerOnStart?: boolean\n}\n\n/**\n * 回调条目(带 ID)\n */\ninterface CallbackEntryWithId<T = unknown> extends MessageCallbackEntry<T> {\n /** 回调ID */\n id: string\n}\n\n/**\n * SharedWorker 管理器类\n */\nexport class SharedWorkerManager {\n private static readonly WORKER_NAME = 'dj-common-websocket-worker'\n\n /** SharedWorker 实例 */\n private worker: SharedWorker | null = null\n\n /** MessagePort 实例 */\n private port: MessagePort | null = null\n\n /** 标签页ID */\n private readonly tabId: string\n\n /** 回调列表 */\n private callbacks: Map<string, CallbackEntryWithId> = new Map()\n\n /** 回调ID计数器 */\n private callbackIdCounter = 0\n\n /** 是否已连接 */\n private connected = false\n\n /** 是否已初始化可见性监听 */\n private visibilityListenerInitialized = false\n\n /** 配置 */\n private config: SharedWorkerManagerConfig\n\n /** 日志器 */\n private readonly logger: Logger\n\n /** 连接回调 */\n private onConnectedCallback: (() => void) | null = null\n\n /** 断开回调 */\n private onDisconnectedCallback: (() => void) | null = null\n\n /** 错误回调 */\n private onErrorCallback: ((error: ErrorPayload) => void) | null = null\n\n /** 身份冲突回调 */\n private onAuthConflictCallback: ((conflict: AuthConflictPayload) => void) | null = null\n\n /** 标签页心跳定时器(用于让 Worker 能识别已关闭标签页) */\n private pingTimer: ReturnType<typeof globalThis.setInterval> | null = null\n\n /** 是否已初始化卸载监听 */\n private unloadListenerInitialized = false\n\n /** 是否已初始化网络状态监听 */\n private networkListenerInitialized = false\n\n /**\n * 构造函数\n */\n constructor(config: SharedWorkerManagerConfig) {\n this.config = config\n this.tabId = this.generateTabId()\n this.logger = new Logger('SharedWorkerManager', config.logLevel ?? 'warn')\n }\n\n /**\n * 生成标签页ID\n */\n private generateTabId(): string {\n return `tab_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n }\n\n /**\n * 发送重置命令到现有 Worker(断开 WebSocket 并清理状态,但不终止 Worker)\n */\n private async sendResetToExistingWorker(workerScriptUrl: string): Promise<void> {\n try {\n // 连接到现有的 SharedWorker\n const existing = new SharedWorker(workerScriptUrl, { name: SharedWorkerManager.WORKER_NAME })\n const port = existing.port\n port.start()\n port.postMessage({\n type: 'TAB_FORCE_RESET' as TabToWorkerMessageType,\n payload: { reason: 'force_new_start' },\n tabId: this.tabId,\n timestamp: Date.now(),\n } satisfies TabToWorkerMessage)\n // 等待消息发送完成后再关闭 port\n await new Promise((resolve) => setTimeout(resolve, 100))\n port.close()\n this.logger.debug('[SharedWorkerManager] 已发送重置命令到现有 Worker')\n } catch (error) {\n this.logger.warn('[SharedWorkerManager] 发送重置命令失败(可忽略)', error)\n }\n }\n\n /**\n * 启动 SharedWorker 连接\n */\n async start(): Promise<boolean> {\n try {\n this.logger.debug('[SharedWorkerManager] 开始启动 SharedWorker')\n\n // 创建 Worker 脚本 URL(必须跨标签页一致,否则 SharedWorker 无法复用)\n // 注意:Blob URL 每次都会不同,因此不能用于 SharedWorker 复用。\n const workerScriptUrl = this.getWorkerScriptDataUrl()\n this.logger.debug(`[SharedWorkerManager] Worker Script URL 创建成功: ${workerScriptUrl.slice(0, 60)}...`)\n\n // 若要求强制重置,则先发送重置命令让 Worker 断开 WebSocket 并清理状态\n if (this.config.forceNewWorkerOnStart) {\n this.logger.debug('[SharedWorkerManager] forceNewWorkerOnStart=true,发送重置命令')\n await this.sendResetToExistingWorker(workerScriptUrl)\n }\n\n // 创建 SharedWorker(使用固定的 name,所有标签页共享同一个 Worker)\n this.logger.debug('[SharedWorkerManager] 正在创建 SharedWorker 实例...')\n this.worker = new SharedWorker(workerScriptUrl, {\n name: SharedWorkerManager.WORKER_NAME,\n // 注意:不使用 type: 'module',因为 Blob URL 作为 module 有 CORS 限制\n })\n this.logger.debug('[SharedWorkerManager] ✅ SharedWorker 实例创建成功')\n\n this.port = this.worker.port\n this.port.onmessage = this.handleWorkerMessage.bind(this)\n // MessagePort 没有 onerror,错误会在 worker 中抛出\n this.port.start()\n this.logger.debug('[SharedWorkerManager] ✅ MessagePort 已启动')\n\n // 设置页面可见性监听\n this.setupVisibilityListener()\n\n // 设置页面卸载监听(页面关闭/刷新时通知 Worker 及时移除 tab)\n this.setupUnloadListener()\n\n // 设置网络状态监听(网络恢复时通知 Worker 重连)\n this.setupNetworkListener()\n\n // 发送初始化消息(只发送可序列化的配置项)\n const serializableConfig = {\n heartbeatInterval: this.config.config.heartbeatInterval,\n maxReconnectAttempts: this.config.config.maxReconnectAttempts,\n reconnectDelay: this.config.config.reconnectDelay,\n reconnectDelayMax: this.config.config.reconnectDelayMax,\n autoReconnect: this.config.config.autoReconnect,\n logLevel: this.config.config.logLevel,\n }\n\n this.sendToWorker(\n 'TAB_INIT' as TabToWorkerMessageType,\n {\n url: this.config.url,\n userId: this.config.userId,\n token: this.config.token,\n isVisible: !document.hidden,\n config: serializableConfig,\n sharedWorkerIdleTimeout: this.config.sharedWorkerIdleTimeout,\n } as InitPayload\n )\n\n // 启动与 Worker 的轻量心跳(用于 Worker 回收已关闭标签页)\n this.startPing()\n\n this.logger.info('[SharedWorkerManager] SharedWorker 已启动')\n return true\n } catch (error) {\n this.logger.error('[SharedWorkerManager] 启动 SharedWorker 失败', error)\n return false\n }\n }\n\n /**\n * 停止 SharedWorker 连接(只断开当前标签页,不影响其他标签页)\n */\n stop(): void {\n this.logger.debug('[SharedWorkerManager] 停止当前标签页的 SharedWorker 连接')\n\n // 保存 port 引用,用于延迟关闭\n const portToClose = this.port\n\n // 发送断开消息,告知 Worker 移除当前标签页\n if (this.port) {\n this.sendToWorker('TAB_DISCONNECT' as TabToWorkerMessageType, {})\n }\n\n // 停止心跳\n this.stopPing()\n\n // 移除可见性监听\n this.removeVisibilityListener()\n\n // 移除卸载监听\n this.removeUnloadListener()\n\n // 移除网络状态监听\n this.removeNetworkListener()\n\n // 清理引用(但延迟关闭 port,确保消息发送完成)\n this.port = null\n this.worker = null\n this.connected = false\n this.callbacks.clear()\n\n // 延迟关闭 port,确保 postMessage 消息被发送出去\n if (portToClose) {\n setTimeout(() => {\n try {\n portToClose.close()\n } catch {\n // 忽略关闭错误\n }\n }, 100)\n }\n }\n\n /**\n * 强制关闭 Worker(会影响所有标签页,用于退出登录)\n */\n forceShutdown(): void {\n this.logger.debug('[SharedWorkerManager] 强制关闭 Worker(退出登录)')\n\n // 保存 port 引用,用于延迟关闭\n const portToClose = this.port\n\n // 发送强制关闭命令\n if (this.port) {\n this.sendToWorker('TAB_FORCE_SHUTDOWN' as TabToWorkerMessageType, {\n reason: 'logout',\n })\n }\n\n // 停止心跳\n this.stopPing()\n\n // 移除可见性监听\n this.removeVisibilityListener()\n\n // 移除卸载监听\n this.removeUnloadListener()\n\n // 移除网络状态监听\n this.removeNetworkListener()\n\n // 清理引用\n this.port = null\n this.worker = null\n this.connected = false\n this.callbacks.clear()\n\n // 延迟关闭 port\n if (portToClose) {\n setTimeout(() => {\n try {\n portToClose.close()\n } catch {\n // 忽略关闭错误\n }\n }, 100)\n }\n }\n\n /**\n * 发送消息到服务器\n */\n send(data: string | object): void {\n if (!this.port) {\n this.logger.warn('[SharedWorkerManager] MessagePort 未初始化,无法发送消息')\n return\n }\n\n const payload: SendPayload = { data }\n this.sendToWorker('TAB_SEND' as TabToWorkerMessageType, payload)\n }\n\n /**\n * 注册消息回调\n */\n registerCallback<T = unknown>(entry: MessageCallbackEntry<T>): string {\n const callbackId = `callback_${this.callbackIdCounter++}`\n const entryWithId: CallbackEntryWithId<T> = {\n ...entry,\n id: callbackId,\n }\n\n this.callbacks.set(callbackId, entryWithId as CallbackEntryWithId)\n\n // 通知 Worker\n if (this.port) {\n const payload: RegisterCallbackPayload = {\n type: entry.type,\n callbackId,\n }\n this.sendToWorker('TAB_REGISTER_CALLBACK' as TabToWorkerMessageType, payload)\n this.logger.debug(`[SharedWorkerManager] ✅ 已发送注册消息到 Worker: ${entry.type} (${callbackId})`)\n } else {\n this.logger.warn(`[SharedWorkerManager] ⚠️ port 未初始化,无法发送注册消息: ${entry.type}`)\n }\n\n this.logger.debug(\n `[SharedWorkerManager] 注册回调: ${entry.type} (${callbackId}), 当前回调总数: ${this.callbacks.size}`\n )\n return callbackId\n }\n\n /**\n * 取消注册消息回调\n */\n unregisterCallback(type: string, callback?: MessageCallback): void {\n if (callback) {\n // 移除特定回调\n let callbackId: string | null = null\n\n for (const [id, entry] of this.callbacks.entries()) {\n if (entry.type === type && entry.callback === callback) {\n callbackId = id\n this.callbacks.delete(id)\n break\n }\n }\n\n if (callbackId && this.port) {\n const payload: UnregisterCallbackPayload = {\n type,\n callbackId,\n }\n this.sendToWorker('TAB_UNREGISTER_CALLBACK' as TabToWorkerMessageType, payload)\n }\n\n this.logger.debug(`[SharedWorkerManager] 取消注册回调: ${type} (${callbackId})`)\n } else {\n // 移除该类型的所有回调\n const callbackIds: string[] = []\n\n for (const [id, entry] of this.callbacks.entries()) {\n if (entry.type === type) {\n callbackIds.push(id)\n this.callbacks.delete(id)\n }\n }\n\n if (this.port) {\n const payload: UnregisterCallbackPayload = { type }\n this.sendToWorker('TAB_UNREGISTER_CALLBACK' as TabToWorkerMessageType, payload)\n }\n\n this.logger.debug(`[SharedWorkerManager] 取消注册所有 ${type} 类型回调 (${callbackIds.length} 个)`)\n }\n }\n\n /**\n * 清空所有回调\n */\n clearCallbacks(): void {\n for (const entry of this.callbacks.values()) {\n if (this.port) {\n const payload: UnregisterCallbackPayload = {\n type: entry.type,\n callbackId: entry.id,\n }\n this.sendToWorker('TAB_UNREGISTER_CALLBACK' as TabToWorkerMessageType, payload)\n }\n }\n\n this.callbacks.clear()\n this.logger.debug('[SharedWorkerManager] 已清空所有回调')\n }\n\n /**\n * 是否已连接\n */\n isConnected(): boolean {\n return this.connected\n }\n\n /**\n * 设置连接回调\n */\n onConnected(callback: () => void): void {\n this.onConnectedCallback = callback\n }\n\n /**\n * 设置断开回调\n */\n onDisconnected(callback: () => void): void {\n this.onDisconnectedCallback = callback\n }\n\n /**\n * 设置错误回调\n */\n onError(callback: (error: ErrorPayload) => void): void {\n this.onErrorCallback = callback\n }\n\n /**\n * 设置身份冲突回调\n */\n onAuthConflict(callback: (conflict: AuthConflictPayload) => void): void {\n this.onAuthConflictCallback = callback\n }\n\n /**\n * 处理来自 Worker 的消息\n */\n private handleWorkerMessage(event: MessageEvent): void {\n const message = event.data as WorkerToTabMessage\n\n this.logger.debug(`[SharedWorkerManager] 📬 收到 Worker 消息, type: ${message.type}`)\n\n switch (message.type) {\n case 'WORKER_CONNECTED' as WorkerToTabMessageType:\n this.connected = true\n this.logger.info('[SharedWorkerManager] ✅ WebSocket 已连接')\n this.onConnectedCallback?.()\n break\n\n case 'WORKER_DISCONNECTED' as WorkerToTabMessageType:\n this.connected = false\n this.logger.info('[SharedWorkerManager] WebSocket 已断开')\n this.onDisconnectedCallback?.()\n break\n\n case 'WORKER_MESSAGE' as WorkerToTabMessageType:\n // 始终打印每一条来自 Worker 的服务器消息,便于排查“回调未注册/未匹配”导致页面无回显的问题\n try {\n const payload = message.payload as ServerMessagePayload\n // 用 console.log 确保即使 logLevel 较高也能看到\n\n console.log('[SharedWorkerManager] 📨 收到服务器消息(经 Worker 转发):', payload?.message)\n this.logger.info('[SharedWorkerManager] 📨 收到服务器消息(经 Worker 转发)', payload?.message)\n this.logger.debug('[SharedWorkerManager] 🧾 原始消息 data:', payload?.data)\n } catch (error) {\n this.logger.warn('[SharedWorkerManager] 打印服务器消息失败', error)\n }\n\n this.handleServerMessage(message.payload as ServerMessagePayload)\n break\n\n case 'WORKER_ERROR' as WorkerToTabMessageType:\n this.logger.error('[SharedWorkerManager] Worker 错误', message.payload)\n this.onErrorCallback?.(message.payload as ErrorPayload)\n break\n\n case 'WORKER_AUTH_CONFLICT' as WorkerToTabMessageType:\n this.logger.warn('[SharedWorkerManager] 身份冲突', message.payload)\n this.onAuthConflictCallback?.(message.payload as AuthConflictPayload)\n break\n\n case 'WORKER_PONG' as WorkerToTabMessageType:\n this.logger.debug('[SharedWorkerManager] 收到 PONG')\n break\n\n default:\n this.logger.warn('[SharedWorkerManager] 未知消息类型', message.type)\n }\n }\n\n /**\n * 处理服务器消息\n */\n private handleServerMessage(payload: ServerMessagePayload): void {\n const { message } = payload\n\n this.logger.debug(`[SharedWorkerManager] 📨 收到服务器消息, type: ${message.type}`)\n this.logger.debug(\n `[SharedWorkerManager] 当前注册的回调类型:`,\n Array.from(this.callbacks.values()).map((e) => e.type)\n )\n\n // 触发匹配的回调\n let matchedCount = 0\n for (const entry of this.callbacks.values()) {\n if (entry.type === message.type) {\n matchedCount++\n this.logger.debug(`[SharedWorkerManager] ✅ 匹配到回调 ${entry.type} (${entry.id}),准备执行`)\n try {\n entry.callback(message.data, message)\n this.logger.debug(`[SharedWorkerManager] ✅ 回调执行成功 ${entry.type} (${entry.id})`)\n } catch (error) {\n this.logger.error('[SharedWorkerManager] ❌ 回调执行失败', error)\n }\n }\n }\n\n if (matchedCount === 0) {\n this.logger.warn(`[SharedWorkerManager] ⚠️ 没有匹配的回调: ${message.type}`)\n }\n }\n\n /**\n * 发送消息到 Worker\n */\n private sendToWorker(type: TabToWorkerMessageType, payload: unknown): void {\n if (!this.port) {\n this.logger.warn('[SharedWorkerManager] MessagePort 未初始化,无法发送消息')\n return\n }\n\n const message: TabToWorkerMessage = {\n type,\n payload,\n tabId: this.tabId,\n timestamp: Date.now(),\n }\n\n try {\n this.port.postMessage(message)\n } catch (error) {\n this.logger.error('[SharedWorkerManager] 发送消息到 Worker 失败', error)\n }\n }\n\n /**\n * 设置页面可见性监听\n */\n private setupVisibilityListener(): void {\n if (typeof document === 'undefined' || this.visibilityListenerInitialized) {\n return\n }\n\n document.addEventListener('visibilitychange', this.handleVisibilityChange)\n this.visibilityListenerInitialized = true\n this.logger.debug('[SharedWorkerManager] 已设置页面可见性监听')\n }\n\n /**\n * 移除页面可见性监听\n */\n private removeVisibilityListener(): void {\n if (typeof document === 'undefined' || !this.visibilityListenerInitialized) {\n return\n }\n\n document.removeEventListener('visibilitychange', this.handleVisibilityChange)\n this.visibilityListenerInitialized = false\n this.logger.debug('[SharedWorkerManager] 已移除页面可见性监听')\n }\n\n /**\n * 处理页面可见性变化\n */\n private handleVisibilityChange = (): void => {\n const isVisible = !document.hidden\n this.logger.debug(`[SharedWorkerManager] 页面可见性变化: ${isVisible}`)\n\n if (this.port) {\n const payload: VisibilityPayload = { isVisible }\n this.sendToWorker('TAB_VISIBILITY' as TabToWorkerMessageType, payload)\n }\n }\n\n /**\n * 启动与 Worker 的轻量心跳\n * 目的:当标签页被强制关闭/崩溃时,Worker 可通过超时回收该 tab\n */\n private startPing(): void {\n this.stopPing()\n if (typeof window === 'undefined' || !this.port) return\n\n // 10s 一次足够,开销很小\n this.pingTimer = globalThis.setInterval(() => {\n if (!this.port) return\n this.sendToWorker('TAB_PING' as TabToWorkerMessageType, {})\n }, 10000)\n }\n\n private stopPing(): void {\n if (this.pingTimer !== null) {\n clearInterval(this.pingTimer)\n this.pingTimer = null\n }\n }\n\n /**\n * 设置页面卸载监听(pagehide/beforeunload)\n */\n private setupUnloadListener(): void {\n if (typeof window === 'undefined' || this.unloadListenerInitialized) return\n\n window.addEventListener('pagehide', this.handlePageHide, { capture: true })\n window.addEventListener('beforeunload', this.handlePageHide, { capture: true })\n this.unloadListenerInitialized = true\n this.logger.debug('[SharedWorkerManager] 已设置页面卸载监听')\n }\n\n private removeUnloadListener(): void {\n if (typeof window === 'undefined' || !this.unloadListenerInitialized) return\n window.removeEventListener('pagehide', this.handlePageHide, { capture: true } as unknown as boolean)\n window.removeEventListener('beforeunload', this.handlePageHide, { capture: true } as unknown as boolean)\n this.unloadListenerInitialized = false\n this.logger.debug('[SharedWorkerManager] 已移除页面卸载监听')\n }\n\n private handlePageHide = (): void => {\n // 尽量在页面销毁前通知 Worker 移除 tab\n if (this.port) {\n this.sendToWorker('TAB_DISCONNECT' as TabToWorkerMessageType, {})\n }\n }\n\n // ========== 网络状态监听 ==========\n\n /**\n * 设置网络状态监听\n */\n private setupNetworkListener(): void {\n if (typeof window === 'undefined' || this.networkListenerInitialized) return\n\n window.addEventListener('online', this.handleOnline)\n window.addEventListener('offline', this.handleOffline)\n this.networkListenerInitialized = true\n this.logger.debug('[SharedWorkerManager] 已设置网络状态监听')\n }\n\n /**\n * 移除网络状态监听\n */\n private removeNetworkListener(): void {\n if (typeof window === 'undefined' || !this.networkListenerInitialized) return\n\n window.removeEventListener('online', this.handleOnline)\n window.removeEventListener('offline', this.handleOffline)\n this.networkListenerInitialized = false\n this.logger.debug('[SharedWorkerManager] 已移除网络状态监听')\n }\n\n /**\n * 网络恢复时的处理函数\n */\n private handleOnline = (): void => {\n this.logger.info('[SharedWorkerManager] 网络已恢复,通知 Worker 重连')\n\n // 通知 Worker 网络已恢复,应该重试连接\n if (this.port) {\n this.sendToWorker('TAB_NETWORK_ONLINE' as TabToWorkerMessageType, {})\n }\n }\n\n /**\n * 网络断开时的处理函数\n */\n private handleOffline = (): void => {\n this.logger.info('[SharedWorkerManager] 网络已断开')\n // Worker 会自动处理连接断开,这里只记录日志\n }\n\n /**\n * 获取 Worker 脚本 Data URL\n * 说明:SharedWorker 的复用依据是「脚本 URL + name」,所以必须让不同标签页拿到完全一致的脚本 URL。\n */\n private getWorkerScriptDataUrl(): string {\n const workerCode = this.getWorkerScriptContent()\n this.logger.debug(`[SharedWorkerManager] 正在创建 Worker Data URL, 代码长度: ${workerCode.length}`)\n\n // 兼容中文日志:必须做 UTF-8 base64\n const base64 = this.toBase64Utf8(workerCode)\n return `data:application/javascript;charset=utf-8;base64,${base64}`\n }\n\n /**\n * UTF-8 Base64 编码(避免 btoa 处理非 latin1 时报错)\n */\n private toBase64Utf8(input: string): string {\n try {\n if (typeof TextEncoder !== 'undefined') {\n const bytes = new TextEncoder().encode(input)\n let binary = ''\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i])\n // eslint-disable-next-line no-undef\n return btoa(binary)\n }\n // 兜底:老浏览器\n // eslint-disable-next-line no-undef\n return btoa(unescape(encodeURIComponent(input)))\n } catch (error) {\n this.logger.error('[SharedWorkerManager] ❌ Worker 脚本 base64 编码失败', error)\n throw error\n }\n }\n\n /**\n * 获取 Worker 脚本内容\n * 这里使用占位符,在构建时会被替换为实际的 Worker 代码\n */\n private getWorkerScriptContent(): string {\n // 占位符,构建时会被替换\n const content: string = '__WORKER_SCRIPT_CONTENT__'\n\n // 调试:检查内容是否被替换\n if (content === '__WORKER_SCRIPT_CONTENT__') {\n this.logger.error('[SharedWorkerManager] ❌ Worker 代码未被内联!构建配置有问题')\n throw new Error('Worker script not inlined during build')\n }\n\n this.logger.debug(`[SharedWorkerManager] Worker 脚本长度: ${content.length} 字符`)\n return content\n }\n}\n","import { WebSocketClient, WebSocketConfig, MessageCallbackEntry, MessageCallback } from './WebSocketClient'\nimport { Logger, LogLevel } from './logger'\nimport { SharedWorkerManager } from './SharedWorkerManager'\nimport type { ConnectionMode } from './types'\n\n/**\n * MessageSocket 配置选项\n */\nexport interface MessageSocketConfig extends WebSocketConfig {\n /** WebSocket 服务器地址,默认 '' */\n url?: string\n /** 消息回调列表 */\n callbacks?: MessageCallbackEntry[]\n /** 日志级别 */\n logLevel?: LogLevel\n /** 是否启用页面可见性管理(标签页切换时自动断开/重连),默认 false */\n enableVisibilityManagement?: boolean\n /** 连接模式,默认 'auto' */\n connectionMode?: ConnectionMode\n /** SharedWorker 空闲超时时间(毫秒),默认 30000 */\n sharedWorkerIdleTimeout?: number\n /** 启动时强制新建 SharedWorker(会生成新的 worker 会话名,并尝试关闭旧 worker),默认 false */\n forceNewWorkerOnStart?: boolean\n}\n\n/**\n * MessageSocket 启动选项\n */\nexport interface MessageSocketStartOptions {\n /** 用户ID(必需) */\n userId: string\n /** 认证令牌(必需) */\n token: string\n}\n\n/**\n * 消息 Socket 类\n * 基于 WebSocketClient,提供用户认证和消息管理功能\n * 用于获取用户未读消息数量等业务场景\n *\n * @example\n * ```typescript\n * // 启动连接\n * MessageSocket.start({\n * userId: '1234567890',\n * token: 'your-token',\n * })\n *\n * // 停止连接\n * MessageSocket.stop()\n *\n * // 设置配置\n * MessageSocket.setConfig({\n * baseUrl: 'ws://your-server.com',\n * path: '/your/path',\n * })\n *\n * // 初始化时设置回调\n * MessageSocket.setCallbacks([\n * {\n * type: 'UNREAD_COUNT',\n * callback: (payload) => {\n * console.log('未读消息数:', payload)\n * }\n * }\n * ])\n * // 动态注册回调\n * MessageSocket.registerCallbacks({\n * type: 'NEW_MESSAGE',\n * callback: (payload) => {\n * console.log('新消息:', payload)\n * }\n * })\n * ```\n * // logLevel 设置日志级别,默认 'warn',可选 'debug', 'info', 'warn', 'error', 'silent'\n *\n */\nexport class MessageSocket {\n /** 默认配置 */\n private static readonly DEFAULT_CONFIG: Required<MessageSocketConfig> = {\n url: '', // WebSocket 服务器地址,默认 ''\n heartbeatInterval: 25000, // 心跳间隔,默认 25 秒\n maxReconnectAttempts: 10, // 最大重连次数,默认 10 次\n reconnectDelay: 3000, // 重连延迟,默认 3 秒\n reconnectDelayMax: 10000, // 最大重连延迟,默认 10 秒\n autoReconnect: true, // 是否自动重连,默认 true\n callbacks: [], // 初始消息回调列表,默认 []\n heartbeatMessage: () => ({\n type: 'PING',\n timestamp: Date.now(), // 心跳消息,默认 { type: 'PING', timestamp: Date.now() }\n }),\n logLevel: 'warn', // 日志级别,默认 'warn'\n enableVisibilityManagement: false, // 是否启用页面可见性管理,默认 false\n connectionMode: 'auto', // 连接模式,默认 'auto'\n sharedWorkerIdleTimeout: 30000, // SharedWorker 空闲超时时间,默认 30 秒\n forceNewWorkerOnStart: false, // 启动时强制重置 Worker 状态,默认 false(仅在需要强制刷新连接参数时使用)\n enableNetworkListener: true, // 是否启用网络状态监听(网络恢复时自动重连),默认 true\n }\n\n /** WebSocket 客户端实例 */\n private static client: WebSocketClient | null = null\n\n /** SharedWorker 管理器实例 */\n private static workerManager: SharedWorkerManager | null = null\n\n /** 当前连接模式 */\n private static currentMode: 'sharedWorker' | 'visibility' | 'normal' = 'normal'\n\n private static readonly logger = new Logger('MessageSocket', MessageSocket.DEFAULT_CONFIG.logLevel)\n\n private static updateLoggerLevel(): void {\n MessageSocket.logger.setLevel(MessageSocket.config.logLevel ?? MessageSocket.DEFAULT_CONFIG.logLevel)\n }\n\n /** 当前用户ID */\n private static currentUserId: string | null = null\n\n /** 当前token */\n private static currentToken: string | null = null\n\n /** 当前配置 */\n private static config: MessageSocketConfig = { ...MessageSocket.DEFAULT_CONFIG }\n\n /** 是否已初始化页面可见性监听 */\n private static visibilityListenerInitialized = false\n\n /**\n * 页面可见性变化处理器\n * 当标签页不可见时断开连接,可见时重新连接\n */\n private static handleVisibilityChange = (): void => {\n if (typeof document === 'undefined') return\n\n const isVisible = !document.hidden\n\n if (!isVisible) {\n // 页面不可见时断开连接,避免多标签页重复连接\n MessageSocket.logger.info('[MessageSocket1111111111111111111111] 页面不可见,断开连接')\n if (MessageSocket.client) {\n MessageSocket.client.disconnect()\n }\n } else {\n // 页面可见时重新连接\n if (MessageSocket.currentUserId && MessageSocket.currentToken) {\n MessageSocket.logger.info('[MessageSocket1111111111111111111111] 页面可见,尝试重新连接')\n // 检查是否已经有活跃连接\n if (!MessageSocket.client || !MessageSocket.client.isConnected()) {\n MessageSocket.start({\n userId: MessageSocket.currentUserId,\n token: MessageSocket.currentToken,\n })\n }\n }\n }\n }\n\n /**\n * 初始化页面可见性监听\n */\n private static initVisibilityListener(): void {\n if (typeof document === 'undefined' || MessageSocket.visibilityListenerInitialized) {\n return\n }\n\n document.addEventListener('visibilitychange', MessageSocket.handleVisibilityChange)\n MessageSocket.visibilityListenerInitialized = true\n MessageSocket.logger.debug('[MessageSocket] 已初始化页面可见性监听')\n }\n\n /**\n * 移除页面可见性监听\n */\n private static removeVisibilityListener(): void {\n if (typeof document === 'undefined' || !MessageSocket.visibilityListenerInitialized) {\n return\n }\n\n document.removeEventListener('visibilitychange', MessageSocket.handleVisibilityChange)\n MessageSocket.visibilityListenerInitialized = false\n MessageSocket.logger.debug('[MessageSocket] 已移除页面可见性监听')\n }\n\n /**\n * 检测 SharedWorker 支持\n */\n private static detectSharedWorkerSupport(): boolean {\n return typeof SharedWorker !== 'undefined' && typeof Blob !== 'undefined'\n }\n\n /**\n * 检测 Visibility API 支持\n */\n private static detectVisibilitySupport(): boolean {\n return typeof document !== 'undefined' && typeof document.hidden !== 'undefined'\n }\n\n /**\n * 决定连接模式\n */\n private static determineConnectionMode(): 'sharedWorker' | 'visibility' | 'normal' {\n const mode = MessageSocket.config.connectionMode || 'auto'\n\n if (mode === 'auto') {\n // 自动选择最佳模式\n if (this.detectSharedWorkerSupport()) {\n MessageSocket.logger.info('[MessageSocket] 自动选择 SharedWorker 模式')\n return 'sharedWorker'\n }\n if (this.detectVisibilitySupport() && MessageSocket.config.enableVisibilityManagement) {\n MessageSocket.logger.info('[MessageSocket] 自动选择 Visibility 模式')\n return 'visibility'\n }\n MessageSocket.logger.info('[MessageSocket] 自动选择 Normal 模式')\n return 'normal'\n }\n\n if (mode === 'sharedWorker') {\n if (this.detectSharedWorkerSupport()) {\n MessageSocket.logger.info('[MessageSocket] 使用 SharedWorker 模式')\n return 'sharedWorker'\n }\n // 降级\n MessageSocket.logger.warn('[MessageSocket] 浏览器不支持 SharedWorker,降级到 Visibility 模式')\n if (this.detectVisibilitySupport() && MessageSocket.config.enableVisibilityManagement) {\n return 'visibility'\n }\n MessageSocket.logger.warn('[MessageSocket] 降级到 Normal 模式')\n return 'normal'\n }\n\n if (mode === 'visibility') {\n if (this.detectVisibilitySupport()) {\n MessageSocket.logger.info('[MessageSocket] 使用 Visibility 模式')\n return 'visibility'\n }\n MessageSocket.logger.warn('[MessageSocket] 浏览器不支持 Visibility API,降级到 Normal 模式')\n return 'normal'\n }\n\n // normal 模式\n MessageSocket.logger.info('[MessageSocket] 使用 Normal 模式')\n return 'normal'\n }\n\n /**\n * 配置 MessageSocket\n * @param config 配置选项\n */\n public static configure(config: Partial<MessageSocketConfig>): void {\n MessageSocket.config = { ...MessageSocket.config, ...config }\n }\n\n /**\n * 设置配置\n * @param config 配置选项\n * @returns MessageSocket\n */\n public static setConfig(config: Partial<MessageSocketConfig>): typeof MessageSocket {\n MessageSocket.config = { ...MessageSocket.config, ...config }\n MessageSocket.updateLoggerLevel()\n if (MessageSocket.config.callbacks && MessageSocket.config.callbacks.length > 0) {\n MessageSocket.setCallbacks(MessageSocket.config.callbacks)\n }\n return MessageSocket\n }\n\n /**\n * 设置回调\n * @param callbacks 回调列表\n * @returns MessageSocket\n */\n public static setCallbacks(callbacks: MessageCallbackEntry[]): typeof MessageSocket {\n if (!callbacks || callbacks.length === 0) {\n this.logger.warn('[MessageSocket] 回调列表为空,无法设置回调')\n return MessageSocket\n }\n\n MessageSocket.config.callbacks = callbacks\n return MessageSocket\n }\n\n /**\n * 启动连接\n * @param options 启动选项\n * 回调在开始的时候注册,这种能\n */\n public static start(options: MessageSocketStartOptions): void {\n if (!MessageSocket.config.url) {\n this.logger.error('[MessageSocket] 缺少配置 url, 请先调用 setConfig 设置配置!')\n return\n }\n\n const { userId, token } = options\n\n if (!userId || !token) {\n this.logger.error('[MessageSocket] 缺少 userId 或 token,无法启动')\n return\n }\n\n this.logger.info('[MessageSocket] 开始连接', userId)\n\n // 决定连接模式\n MessageSocket.currentMode = MessageSocket.determineConnectionMode()\n\n // 根据模式启动\n if (MessageSocket.currentMode === 'sharedWorker') {\n MessageSocket.startWithSharedWorker(options)\n } else if (MessageSocket.currentMode === 'visibility') {\n MessageSocket.startWithVisibility(options)\n } else {\n MessageSocket.startWithNormalMode(options)\n }\n }\n\n /**\n * 使用 SharedWorker 模式启动\n */\n private static async startWithSharedWorker(options: MessageSocketStartOptions): Promise<void> {\n const { userId, token } = options\n\n // 停止旧连接\n MessageSocket.stop()\n\n // 保存当前用户信息\n MessageSocket.currentUserId = userId\n MessageSocket.currentToken = token\n\n // 创建 SharedWorker 管理器\n MessageSocket.workerManager = new SharedWorkerManager({\n url: MessageSocket.config.url!,\n userId,\n token,\n isVisible: typeof document !== 'undefined' ? !document.hidden : true,\n config: MessageSocket.config,\n sharedWorkerIdleTimeout: MessageSocket.config.sharedWorkerIdleTimeout,\n logLevel: MessageSocket.config.logLevel,\n forceNewWorkerOnStart: MessageSocket.config.forceNewWorkerOnStart,\n })\n\n // 设置错误回调(降级)\n MessageSocket.workerManager.onError((error) => {\n MessageSocket.logger.warn('[MessageSocket] SharedWorker 失败,降级到 Visibility 模式', error)\n MessageSocket.currentMode = 'visibility'\n MessageSocket.workerManager = null\n MessageSocket.startWithVisibility(options)\n })\n\n // 启动 Worker(必须先启动,创建 port 后才能注册回调)\n const success = await MessageSocket.workerManager.start()\n\n if (!success) {\n // 降级\n MessageSocket.logger.warn('[MessageSocket] SharedWorker 启动失败,降级到 Visibility 模式')\n MessageSocket.currentMode = 'visibility'\n MessageSocket.workerManager = null\n MessageSocket.startWithVisibility(options)\n return\n }\n\n // 注册已有的回调(必须在 start() 之后,此时 port 已创建)\n if (MessageSocket.config.callbacks && MessageSocket.config.callbacks.length > 0) {\n MessageSocket.config.callbacks.forEach((entry) => {\n MessageSocket.workerManager!.registerCallback(entry)\n })\n }\n }\n\n /**\n * 使用 Visibility 模式启动\n */\n private static startWithVisibility(options: MessageSocketStartOptions): void {\n const { userId, token } = options\n\n // 检查是否可以复用现有连接\n const shouldReuse =\n MessageSocket.client &&\n MessageSocket.client.isConnected() &&\n MessageSocket.currentUserId === userId &&\n MessageSocket.currentToken === token\n\n if (shouldReuse) {\n this.logger.debug('[MessageSocket] 复用现有连接')\n return\n }\n\n // 停止旧连接\n MessageSocket.stop()\n\n // 保存当前用户信息\n MessageSocket.currentUserId = userId\n MessageSocket.currentToken = token\n\n // 构建 WebSocket URL\n const { url, ...clientConfig } = MessageSocket.config\n const targetUrl = `${url}/${userId}?token=${encodeURIComponent(token)}`\n\n // 创建新的 WebSocket 客户端\n MessageSocket.client = new WebSocketClient({\n ...clientConfig,\n url: targetUrl,\n })\n\n // 注册回调(直接注册到 client,不经过 registerCallbacks)\n if (MessageSocket.config.callbacks && MessageSocket.config.callbacks.length > 0) {\n MessageSocket.config.callbacks.forEach((entry) => MessageSocket.client!.on(entry))\n }\n\n // 初始化页面可见性监听\n MessageSocket.initVisibilityListener()\n\n // 连接\n MessageSocket.client.connect()\n }\n\n /**\n * 使用 Normal 模式启动\n */\n private static startWithNormalMode(options: MessageSocketStartOptions): void {\n const { userId, token } = options\n\n // 检查是否可以复用现有连接\n const shouldReuse =\n MessageSocket.client &&\n MessageSocket.client.isConnected() &&\n MessageSocket.currentUserId === userId &&\n MessageSocket.currentToken === token\n\n if (shouldReuse) {\n this.logger.debug('[MessageSocket] 复用现有连接')\n return\n }\n\n // 停止旧连接\n MessageSocket.stop()\n\n // 保存当前用户信息\n MessageSocket.currentUserId = userId\n MessageSocket.currentToken = token\n\n // 构建 WebSocket URL\n const { url, ...clientConfig } = MessageSocket.config\n const targetUrl = `${url}/${userId}?token=${encodeURIComponent(token)}`\n\n // 创建新的 WebSocket 客户端\n MessageSocket.client = new WebSocketClient({\n ...clientConfig,\n url: targetUrl,\n })\n\n // 注册回调(直接注册到 client,不经过 registerCallbacks)\n if (MessageSocket.config.callbacks && MessageSocket.config.callbacks.length > 0) {\n MessageSocket.config.callbacks.forEach((entry) => MessageSocket.client!.on(entry))\n }\n\n // 连接\n MessageSocket.client.connect()\n }\n\n /**\n * 停止连接\n */\n public static stop(): void {\n MessageSocket.logger.info('[MessageSocket] 停止连接')\n if (MessageSocket.client) {\n MessageSocket.client.disconnect()\n MessageSocket.client.clearCallbacks()\n MessageSocket.client = null\n }\n\n if (MessageSocket.workerManager) {\n MessageSocket.workerManager.stop()\n MessageSocket.workerManager = null\n }\n\n // 清理页面可见性监听\n MessageSocket.removeVisibilityListener()\n\n MessageSocket.currentUserId = null\n MessageSocket.currentToken = null\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public static registerCallbacks<T = unknown>(entry: MessageCallbackEntry<T>): void {\n if (!entry) {\n this.logger.warn('[MessageSocket] 注册回调失败,缺少 entry', entry)\n return\n }\n\n if (typeof entry !== 'object') {\n this.logger.warn('[MessageSocket] 注册回调失败,entry 不是对象', entry)\n return\n }\n\n if (typeof entry.callback !== 'function') {\n this.logger.warn('[MessageSocket] 注册回调失败,callback 不是函数', entry)\n return\n }\n\n if (typeof entry.type !== 'string') {\n this.logger.warn('[MessageSocket] 注册回调失败,type 不是字符串', entry)\n return\n }\n\n // 根据当前模式注册回调\n if (MessageSocket.currentMode === 'sharedWorker' && MessageSocket.workerManager) {\n MessageSocket.workerManager.registerCallback(entry)\n } else if (MessageSocket.client) {\n MessageSocket.client.on(entry)\n } else {\n this.logger.warn('[MessageSocket] 无可用连接,无法注册回调')\n }\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选)\n */\n public static unregisterCallbacks<T = unknown>(type: string, callback?: (data: T, message?: unknown) => void): void {\n // 根据当前模式取消注册\n if (MessageSocket.currentMode === 'sharedWorker' && MessageSocket.workerManager) {\n MessageSocket.workerManager.unregisterCallback(type, callback as MessageCallback)\n } else if (MessageSocket.client) {\n MessageSocket.client.off(type, callback)\n }\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public static send(data: string | object): void {\n // 根据当前模式发送\n if (MessageSocket.currentMode === 'sharedWorker' && MessageSocket.workerManager) {\n MessageSocket.workerManager.send(data)\n } else if (MessageSocket.client) {\n MessageSocket.client.send(data)\n } else {\n this.logger.warn('[MessageSocket] 无可用连接,无法发送消息')\n }\n }\n\n /**\n * 获取连接状态\n */\n public static getReadyState(): number {\n return MessageSocket.client?.getReadyState() ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public static isConnected(): boolean {\n if (MessageSocket.currentMode === 'sharedWorker' && MessageSocket.workerManager) {\n return MessageSocket.workerManager.isConnected()\n }\n return MessageSocket.client?.isConnected() ?? false\n }\n\n /**\n * 获取当前连接模式\n */\n public static getConnectionMode(): 'sharedWorker' | 'visibility' | 'normal' {\n return MessageSocket.currentMode\n }\n\n /**\n * 获取当前用户ID\n */\n public static getCurrentUserId(): string | null {\n return MessageSocket.currentUserId\n }\n\n /**\n * 获取当前token\n */\n public static getCurrentToken(): string | null {\n return MessageSocket.currentToken\n }\n}\n"],"names":["LOG_LEVEL_PRIORITY","debug","info","warn","error","silent","Logger","constructor","name","level","this","setLevel","getLevel","values","logAtLevel","console","shouldLog","writer","WebSocketClient","config","socket","heartbeatTimer","reconnectTimer","callbackList","currentUrl","reconnectAttempts","manualClose","networkListenerInitialized","handleOnline","logger","isConnected","clearTimeout","autoReconnect","connect","handleOffline","DEFAULT_CONFIG","logLevel","updateConfig","url","targetUrl","initNetworkListener","WebSocket","onopen","startHeartbeat","onOpen","onmessage","event","handleIncoming","data","onclose","code","reason","stopHeartbeat","onClose","scheduleReconnect","onerror","onError","maxReconnectAttempts","delay","Math","min","reconnectDelay","reconnectDelayMax","window","setTimeout","disconnect","removeNetworkListener","close","send","readyState","OPEN","message","JSON","stringify","parse","type","filter","entry","forEach","callback","onMessage","setInterval","heartbeatData","heartbeatMessage","heartbeatInterval","clearInterval","on","typeOrEntry","push","off","clearCallbacks","getReadyState","CLOSED","_event","_message","enableNetworkListener","addEventListener","removeEventListener","timestamp","Date","now","SharedWorkerManager","worker","port","callbacks","Map","callbackIdCounter","connected","visibilityListenerInitialized","onConnectedCallback","onDisconnectedCallback","onErrorCallback","onAuthConflictCallback","pingTimer","unloadListenerInitialized","handleVisibilityChange","isVisible","document","hidden","payload","sendToWorker","handlePageHide","tabId","generateTabId","random","toString","substring","sendResetToExistingWorker","workerScriptUrl","SharedWorker","WORKER_NAME","start","postMessage","Promise","resolve","getWorkerScriptDataUrl","slice","forceNewWorkerOnStart","handleWorkerMessage","bind","setupVisibilityListener","setupUnloadListener","setupNetworkListener","serializableConfig","userId","token","sharedWorkerIdleTimeout","startPing","stop","portToClose","stopPing","removeVisibilityListener","removeUnloadListener","clear","forceShutdown","registerCallback","callbackId","entryWithId","id","set","size","unregisterCallback","entries","delete","callbackIds","length","onConnected","onDisconnected","onAuthConflict","log","handleServerMessage","Array","from","map","e","matchedCount","globalThis","capture","workerCode","getWorkerScriptContent","toBase64Utf8","input","TextEncoder","bytes","encode","binary","i","String","fromCharCode","btoa","unescape","encodeURIComponent","content","MessageSocket","updateLoggerLevel","initVisibilityListener","detectSharedWorkerSupport","Blob","detectVisibilitySupport","determineConnectionMode","mode","connectionMode","enableVisibilityManagement","configure","setConfig","setCallbacks","options","currentMode","startWithSharedWorker","startWithVisibility","startWithNormalMode","currentUserId","currentToken","workerManager","client","clientConfig","registerCallbacks","unregisterCallbacks","getConnectionMode","getCurrentUserId","getCurrentToken"],"mappings":"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"}