@brewer/dj-common 1.0.0-beta.14 → 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/MessageSocket.cjs.js +1 -1
- package/dist/MessageSocket.cjs.js.map +1 -1
- package/dist/MessageSocket.d.ts.map +1 -1
- package/dist/MessageSocket.esm.js +1 -1
- package/dist/MessageSocket.esm.js.map +1 -1
- package/dist/SharedWorkerManager.cjs.js +1 -1
- package/dist/SharedWorkerManager.cjs.js.map +1 -1
- package/dist/SharedWorkerManager.d.ts +18 -0
- package/dist/SharedWorkerManager.d.ts.map +1 -1
- package/dist/SharedWorkerManager.esm.js +1 -1
- package/dist/SharedWorkerManager.esm.js.map +1 -1
- package/dist/WebSocketClient.cjs.js +1 -1
- package/dist/WebSocketClient.cjs.js.map +1 -1
- package/dist/WebSocketClient.d.ts +20 -0
- package/dist/WebSocketClient.d.ts.map +1 -1
- package/dist/WebSocketClient.esm.js +1 -1
- package/dist/WebSocketClient.esm.js.map +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/types.cjs.js +1 -1
- package/dist/types.cjs.js.map +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.esm.js +1 -1
- package/dist/types.esm.js.map +1 -1
- package/dist/worker-script.cjs.js +1 -1
- package/dist/worker-script.cjs.js.map +1 -1
- package/dist/worker-script.d.ts +5 -0
- package/dist/worker-script.d.ts.map +1 -1
- package/dist/worker-script.esm.js +1 -1
- package/dist/worker-script.esm.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
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.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;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 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)}}n.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,heartbeatMessage:()=>({type:"PING",timestamp:Date.now()}),logLevel:"warn"};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.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.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();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.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.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] 已移除页面卸载监听"))}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}\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 default:\n console.warn('[SharedWorker] 未知消息类型', message.type);\n }\n };\n port.start();\n};\n";return this.logger.debug("[SharedWorkerManager] Worker 脚本长度: 20877 字符"),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:a,...o}=s.config,i=`${a}/${t}?token=${encodeURIComponent(r)}`;s.client=new n({...o,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:a,...o}=s.config,i=`${a}/${t}?token=${encodeURIComponent(r)}`;s.client=new n({...o,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},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())};export{s as MessageSocket};
|
|
1
|
+
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())};export{s as MessageSocket};
|
|
2
2
|
//# sourceMappingURL=MessageSocket.esm.js.map
|