@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/types.d.ts CHANGED
@@ -20,7 +20,13 @@ export declare enum TabToWorkerMessageType {
20
20
  /** 断开连接 */
21
21
  TAB_DISCONNECT = "TAB_DISCONNECT",
22
22
  /** PING 心跳 */
23
- TAB_PING = "TAB_PING"
23
+ TAB_PING = "TAB_PING",
24
+ /** 强制关闭 Worker(用于退出登录/强制重置连接) */
25
+ TAB_FORCE_SHUTDOWN = "TAB_FORCE_SHUTDOWN",
26
+ /** 强制重置 Worker 状态(断开 WebSocket 但不终止 Worker) */
27
+ TAB_FORCE_RESET = "TAB_FORCE_RESET",
28
+ /** 网络已恢复(通知 Worker 重试连接) */
29
+ TAB_NETWORK_ONLINE = "TAB_NETWORK_ONLINE"
24
30
  }
25
31
  /**
26
32
  * Worker 到标签页的消息类型
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.esm.js CHANGED
@@ -1,2 +1,2 @@
1
- var R,E;!function(R){R.TAB_INIT="TAB_INIT",R.TAB_SEND="TAB_SEND",R.TAB_VISIBILITY="TAB_VISIBILITY",R.TAB_REGISTER_CALLBACK="TAB_REGISTER_CALLBACK",R.TAB_UNREGISTER_CALLBACK="TAB_UNREGISTER_CALLBACK",R.TAB_DISCONNECT="TAB_DISCONNECT",R.TAB_PING="TAB_PING"}(R||(R={})),function(R){R.WORKER_READY="WORKER_READY",R.WORKER_MESSAGE="WORKER_MESSAGE",R.WORKER_CONNECTED="WORKER_CONNECTED",R.WORKER_DISCONNECTED="WORKER_DISCONNECTED",R.WORKER_ERROR="WORKER_ERROR",R.WORKER_AUTH_CONFLICT="WORKER_AUTH_CONFLICT",R.WORKER_PONG="WORKER_PONG"}(E||(E={}));export{R as TabToWorkerMessageType,E as WorkerToTabMessageType};
1
+ var E,R;!function(E){E.TAB_INIT="TAB_INIT",E.TAB_SEND="TAB_SEND",E.TAB_VISIBILITY="TAB_VISIBILITY",E.TAB_REGISTER_CALLBACK="TAB_REGISTER_CALLBACK",E.TAB_UNREGISTER_CALLBACK="TAB_UNREGISTER_CALLBACK",E.TAB_DISCONNECT="TAB_DISCONNECT",E.TAB_PING="TAB_PING",E.TAB_FORCE_SHUTDOWN="TAB_FORCE_SHUTDOWN",E.TAB_FORCE_RESET="TAB_FORCE_RESET",E.TAB_NETWORK_ONLINE="TAB_NETWORK_ONLINE"}(E||(E={})),function(E){E.WORKER_READY="WORKER_READY",E.WORKER_MESSAGE="WORKER_MESSAGE",E.WORKER_CONNECTED="WORKER_CONNECTED",E.WORKER_DISCONNECTED="WORKER_DISCONNECTED",E.WORKER_ERROR="WORKER_ERROR",E.WORKER_AUTH_CONFLICT="WORKER_AUTH_CONFLICT",E.WORKER_PONG="WORKER_PONG"}(R||(R={}));export{E as TabToWorkerMessageType,R as WorkerToTabMessageType};
2
2
  //# sourceMappingURL=types.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.esm.js","sources":["../src/types.ts"],"sourcesContent":["/**\n * SharedWorker 相关类型定义\n * 定义标签页和 Worker 之间的消息协议\n */\n\nimport type { WebSocketConfig } from './WebSocketClient'\n\n/**\n * 标签页到 Worker 的消息类型\n */\nexport enum TabToWorkerMessageType {\n /** 初始化连接 */\n TAB_INIT = 'TAB_INIT',\n /** 发送消息到服务器 */\n TAB_SEND = 'TAB_SEND',\n /** 标签页可见性变化 */\n TAB_VISIBILITY = 'TAB_VISIBILITY',\n /** 注册消息回调 */\n TAB_REGISTER_CALLBACK = 'TAB_REGISTER_CALLBACK',\n /** 取消注册消息回调 */\n TAB_UNREGISTER_CALLBACK = 'TAB_UNREGISTER_CALLBACK',\n /** 断开连接 */\n TAB_DISCONNECT = 'TAB_DISCONNECT',\n /** PING 心跳 */\n TAB_PING = 'TAB_PING',\n}\n\n/**\n * Worker 到标签页的消息类型\n */\nexport enum WorkerToTabMessageType {\n /** Worker 已就绪 */\n WORKER_READY = 'WORKER_READY',\n /** 服务器消息 */\n WORKER_MESSAGE = 'WORKER_MESSAGE',\n /** WebSocket 已连接 */\n WORKER_CONNECTED = 'WORKER_CONNECTED',\n /** WebSocket 已断开 */\n WORKER_DISCONNECTED = 'WORKER_DISCONNECTED',\n /** 连接错误 */\n WORKER_ERROR = 'WORKER_ERROR',\n /** 身份冲突警告 */\n WORKER_AUTH_CONFLICT = 'WORKER_AUTH_CONFLICT',\n /** PONG 响应 */\n WORKER_PONG = 'WORKER_PONG',\n}\n\n/**\n * 标签页到 Worker 的消息\n */\nexport interface TabToWorkerMessage {\n /** 消息类型 */\n type: TabToWorkerMessageType\n /** 消息负载 */\n payload?: unknown\n /** 标签页ID */\n tabId: string\n /** 时间戳 */\n timestamp: number\n}\n\n/**\n * Worker 到标签页的消息\n */\nexport interface WorkerToTabMessage {\n /** 消息类型 */\n type: WorkerToTabMessageType\n /** 消息负载 */\n payload?: unknown\n /** 时间戳 */\n timestamp: number\n}\n\n/**\n * 初始化负载\n */\nexport interface InitPayload {\n /** WebSocket 服务器地址 */\n url: string\n /** 用户ID */\n userId: string\n /** 认证令牌 */\n token: string\n /** 标签页是否可见 */\n isVisible: boolean\n /** WebSocket 配置 */\n config: WebSocketConfig\n /** SharedWorker 空闲超时时间(毫秒) */\n sharedWorkerIdleTimeout?: number\n}\n\n/**\n * 发送消息负载\n */\nexport interface SendPayload {\n /** 消息数据 */\n data: string | object\n}\n\n/**\n * 可见性变化负载\n */\nexport interface VisibilityPayload {\n /** 是否可见 */\n isVisible: boolean\n}\n\n/**\n * 注册回调负载\n */\nexport interface RegisterCallbackPayload {\n /** 消息类型 */\n type: string\n /** 回调ID(用于取消注册) */\n callbackId: string\n}\n\n/**\n * 取消注册回调负载\n */\nexport interface UnregisterCallbackPayload {\n /** 消息类型 */\n type: string\n /** 回调ID(可选,如果不传则移除该类型的所有回调) */\n callbackId?: string\n}\n\n/**\n * 服务器消息负载\n */\nexport interface ServerMessagePayload {\n /** 原始消息数据 */\n data: string\n /** 解析后的消息对象 */\n message: {\n type: string\n data: unknown\n meta?: Record<string, unknown>\n timestamp?: number\n }\n}\n\n/**\n * 错误负载\n */\nexport interface ErrorPayload {\n /** 错误消息 */\n message: string\n /** 错误详情 */\n error?: unknown\n}\n\n/**\n * 身份冲突负载\n */\nexport interface AuthConflictPayload {\n /** 当前连接的用户ID */\n currentUserId: string\n /** 新标签页尝试连接的用户ID */\n newUserId: string\n /** 警告消息 */\n message: string\n}\n\n/**\n * 标签页信息\n */\nexport interface TabInfo {\n /** MessagePort 实例 */\n port: MessagePort\n /** 标签页ID */\n tabId: string\n /** 是否可见 */\n isVisible: boolean\n /** 注册的消息类型 */\n registeredTypes: Set<string>\n /** 回调ID映射表(callbackId -> type) */\n callbackMap: Map<string, string>\n}\n\n/**\n * 连接模式\n */\nexport type ConnectionMode = 'auto' | 'sharedWorker' | 'visibility' | 'normal'\n\n/**\n * SharedWorker 配置\n */\nexport interface SharedWorkerConfig {\n /** 连接模式,默认 'auto' */\n connectionMode?: ConnectionMode\n /** SharedWorker 空闲超时时间(毫秒),默认 30000 */\n sharedWorkerIdleTimeout?: number\n}\n"],"names":["TabToWorkerMessageType","WorkerToTabMessageType"],"mappings":"IAUYA,EAoBAC,GApBZ,SAAYD,GAEVA,EAAA,SAAA,WAEAA,EAAA,SAAA,WAEAA,EAAA,eAAA,iBAEAA,EAAA,sBAAA,wBAEAA,EAAA,wBAAA,0BAEAA,EAAA,eAAA,iBAEAA,EAAA,SAAA,UACD,CAfD,CAAYA,IAAAA,EAAsB,CAAA,IAoBlC,SAAYC,GAEVA,EAAA,aAAA,eAEAA,EAAA,eAAA,iBAEAA,EAAA,iBAAA,mBAEAA,EAAA,oBAAA,sBAEAA,EAAA,aAAA,eAEAA,EAAA,qBAAA,uBAEAA,EAAA,YAAA,aACD,CAfD,CAAYA,IAAAA,EAAsB,CAAA"}
1
+ {"version":3,"file":"types.esm.js","sources":["../src/types.ts"],"sourcesContent":["/**\n * SharedWorker 相关类型定义\n * 定义标签页和 Worker 之间的消息协议\n */\n\nimport type { WebSocketConfig } from './WebSocketClient'\n\n/**\n * 标签页到 Worker 的消息类型\n */\nexport enum TabToWorkerMessageType {\n /** 初始化连接 */\n TAB_INIT = 'TAB_INIT',\n /** 发送消息到服务器 */\n TAB_SEND = 'TAB_SEND',\n /** 标签页可见性变化 */\n TAB_VISIBILITY = 'TAB_VISIBILITY',\n /** 注册消息回调 */\n TAB_REGISTER_CALLBACK = 'TAB_REGISTER_CALLBACK',\n /** 取消注册消息回调 */\n TAB_UNREGISTER_CALLBACK = 'TAB_UNREGISTER_CALLBACK',\n /** 断开连接 */\n TAB_DISCONNECT = 'TAB_DISCONNECT',\n /** PING 心跳 */\n TAB_PING = 'TAB_PING',\n /** 强制关闭 Worker(用于退出登录/强制重置连接) */\n TAB_FORCE_SHUTDOWN = 'TAB_FORCE_SHUTDOWN',\n /** 强制重置 Worker 状态(断开 WebSocket 但不终止 Worker) */\n TAB_FORCE_RESET = 'TAB_FORCE_RESET',\n /** 网络已恢复(通知 Worker 重试连接) */\n TAB_NETWORK_ONLINE = 'TAB_NETWORK_ONLINE',\n}\n\n/**\n * Worker 到标签页的消息类型\n */\nexport enum WorkerToTabMessageType {\n /** Worker 已就绪 */\n WORKER_READY = 'WORKER_READY',\n /** 服务器消息 */\n WORKER_MESSAGE = 'WORKER_MESSAGE',\n /** WebSocket 已连接 */\n WORKER_CONNECTED = 'WORKER_CONNECTED',\n /** WebSocket 已断开 */\n WORKER_DISCONNECTED = 'WORKER_DISCONNECTED',\n /** 连接错误 */\n WORKER_ERROR = 'WORKER_ERROR',\n /** 身份冲突警告 */\n WORKER_AUTH_CONFLICT = 'WORKER_AUTH_CONFLICT',\n /** PONG 响应 */\n WORKER_PONG = 'WORKER_PONG',\n}\n\n/**\n * 标签页到 Worker 的消息\n */\nexport interface TabToWorkerMessage {\n /** 消息类型 */\n type: TabToWorkerMessageType\n /** 消息负载 */\n payload?: unknown\n /** 标签页ID */\n tabId: string\n /** 时间戳 */\n timestamp: number\n}\n\n/**\n * Worker 到标签页的消息\n */\nexport interface WorkerToTabMessage {\n /** 消息类型 */\n type: WorkerToTabMessageType\n /** 消息负载 */\n payload?: unknown\n /** 时间戳 */\n timestamp: number\n}\n\n/**\n * 初始化负载\n */\nexport interface InitPayload {\n /** WebSocket 服务器地址 */\n url: string\n /** 用户ID */\n userId: string\n /** 认证令牌 */\n token: string\n /** 标签页是否可见 */\n isVisible: boolean\n /** WebSocket 配置 */\n config: WebSocketConfig\n /** SharedWorker 空闲超时时间(毫秒) */\n sharedWorkerIdleTimeout?: number\n}\n\n/**\n * 发送消息负载\n */\nexport interface SendPayload {\n /** 消息数据 */\n data: string | object\n}\n\n/**\n * 可见性变化负载\n */\nexport interface VisibilityPayload {\n /** 是否可见 */\n isVisible: boolean\n}\n\n/**\n * 注册回调负载\n */\nexport interface RegisterCallbackPayload {\n /** 消息类型 */\n type: string\n /** 回调ID(用于取消注册) */\n callbackId: string\n}\n\n/**\n * 取消注册回调负载\n */\nexport interface UnregisterCallbackPayload {\n /** 消息类型 */\n type: string\n /** 回调ID(可选,如果不传则移除该类型的所有回调) */\n callbackId?: string\n}\n\n/**\n * 服务器消息负载\n */\nexport interface ServerMessagePayload {\n /** 原始消息数据 */\n data: string\n /** 解析后的消息对象 */\n message: {\n type: string\n data: unknown\n meta?: Record<string, unknown>\n timestamp?: number\n }\n}\n\n/**\n * 错误负载\n */\nexport interface ErrorPayload {\n /** 错误消息 */\n message: string\n /** 错误详情 */\n error?: unknown\n}\n\n/**\n * 身份冲突负载\n */\nexport interface AuthConflictPayload {\n /** 当前连接的用户ID */\n currentUserId: string\n /** 新标签页尝试连接的用户ID */\n newUserId: string\n /** 警告消息 */\n message: string\n}\n\n/**\n * 标签页信息\n */\nexport interface TabInfo {\n /** MessagePort 实例 */\n port: MessagePort\n /** 标签页ID */\n tabId: string\n /** 是否可见 */\n isVisible: boolean\n /** 注册的消息类型 */\n registeredTypes: Set<string>\n /** 回调ID映射表(callbackId -> type) */\n callbackMap: Map<string, string>\n}\n\n/**\n * 连接模式\n */\nexport type ConnectionMode = 'auto' | 'sharedWorker' | 'visibility' | 'normal'\n\n/**\n * SharedWorker 配置\n */\nexport interface SharedWorkerConfig {\n /** 连接模式,默认 'auto' */\n connectionMode?: ConnectionMode\n /** SharedWorker 空闲超时时间(毫秒),默认 30000 */\n sharedWorkerIdleTimeout?: number\n}\n"],"names":["TabToWorkerMessageType","WorkerToTabMessageType"],"mappings":"IAUYA,EA0BAC,GA1BZ,SAAYD,GAEVA,EAAA,SAAA,WAEAA,EAAA,SAAA,WAEAA,EAAA,eAAA,iBAEAA,EAAA,sBAAA,wBAEAA,EAAA,wBAAA,0BAEAA,EAAA,eAAA,iBAEAA,EAAA,SAAA,WAEAA,EAAA,mBAAA,qBAEAA,EAAA,gBAAA,kBAEAA,EAAA,mBAAA,oBACD,CArBD,CAAYA,IAAAA,EAAsB,CAAA,IA0BlC,SAAYC,GAEVA,EAAA,aAAA,eAEAA,EAAA,eAAA,iBAEAA,EAAA,iBAAA,mBAEAA,EAAA,oBAAA,sBAEAA,EAAA,aAAA,eAEAA,EAAA,qBAAA,uBAEAA,EAAA,YAAA,aACD,CAfD,CAAYA,IAAAA,EAAsB,CAAA"}
@@ -1,2 +1,2 @@
1
- "use strict";const e="WORKER_MESSAGE",t="WORKER_CONNECTED",s="WORKER_DISCONNECTED",r="WORKER_ERROR",o="WORKER_AUTH_CONFLICT";const a=new class{constructor(){this.tabs=new Map,this.socket=null,this.idleTimer=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.reconnectAttempts=0,this.manualClose=!1,this.currentUrl=null,this.currentUserId=null,this.config=null,this.sharedWorkerIdleTimeout=3e4}addTab(e,s){const{tabId:r,payload:a}=s,i=a;if(this.currentUserId&&this.currentUserId!==i.userId){const t={currentUserId:this.currentUserId,newUserId:i.userId,message:`检测到不同用户身份:当前连接用户为 ${this.currentUserId},新标签页尝试使用用户 ${i.userId} 连接。将复用现有连接。`};this.sendToTab(e,o,t),console.warn("[SharedWorker]",t.message)}const c={port:e,tabId:r,isVisible:i.isVisible,registeredTypes:new Set,callbackMap:new Map};this.tabs.set(r,c),console.log(`[SharedWorker] 标签页已添加: ${r}, 当前标签页数量: ${this.tabs.size}`),this.socket&&this.socket.readyState===WebSocket.OPEN?this.sendToTab(e,t,{}):(this.currentUrl=`${i.url}/${i.userId}?token=${encodeURIComponent(i.token)}`,this.currentUserId=i.userId,this.config=i.config,this.sharedWorkerIdleTimeout=i.sharedWorkerIdleTimeout??3e4,this.connect()),this.resetIdleTimer()}removeTab(e){this.tabs.delete(e),console.log(`[SharedWorker] 标签页已移除: ${e}, 剩余标签页数量: ${this.tabs.size}`),0===this.tabs.size?(console.log(`[SharedWorker] 所有标签页已关闭,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`),this.startIdleTimer()):this.checkAllTabsVisibility()}updateTabVisibility(e,t){const s=this.tabs.get(e);s?(s.isVisible=t,console.log(`[SharedWorker] 标签页 ${e} 可见性更新: ${t}`),this.checkAllTabsVisibility()):console.warn(`[SharedWorker] 标签页不存在: ${e}`)}checkAllTabsVisibility(){if(0===this.tabs.size)return;Array.from(this.tabs.values()).every(e=>!e.isVisible)?(console.log(`[SharedWorker] 所有标签页都不可见,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`),this.startIdleTimer()):(console.log("[SharedWorker] 至少有一个标签页可见,保持连接"),this.resetIdleTimer())}startIdleTimer(){this.clearIdleTimer(),this.idleTimer=globalThis.setTimeout(()=>{console.log("[SharedWorker] 空闲超时,断开连接"),this.disconnect()},this.sharedWorkerIdleTimeout)}resetIdleTimer(){this.clearIdleTimer()}clearIdleTimer(){null!==this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}connect(){if(this.currentUrl){this.manualClose=!1;try{this.socket=new WebSocket(this.currentUrl),this.socket.onopen=()=>{console.log("[SharedWorker] ✅ WebSocket 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),console.log(`[SharedWorker] 通知 ${this.tabs.size} 个标签页: 已连接`),this.broadcastToAllTabs(t,{})},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{console.log("[SharedWorker] WebSocket 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.broadcastToAllTabs(s,{}),!this.manualClose&&this.config?.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{console.error("[SharedWorker] WebSocket 连接错误",e),this.stopHeartbeat();const t={message:"WebSocket 连接错误",error:e};this.broadcastToAllTabs(r,t)}}catch(e){console.error("[SharedWorker] 创建 WebSocket 连接失败",e);const t={message:"创建 WebSocket 连接失败",error:e};this.broadcastToAllTabs(r,t),this.config?.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else console.error("[SharedWorker] 缺少 WebSocket URL")}scheduleReconnect(){const e=this.config?.maxReconnectAttempts??10,t=this.config?.reconnectDelay??3e3,s=this.config?.reconnectDelayMax??1e4;if(this.reconnectAttempts>=e||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=e&&console.warn("[SharedWorker] 已达到最大重连次数"));this.reconnectAttempts+=1;const r=Math.min(t*this.reconnectAttempts,s);console.log(`[SharedWorker] 将在 ${r}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=globalThis.setTimeout(()=>{this.connect()},r)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.clearIdleTimer(),null!==this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.reconnectAttempts=0}startHeartbeat(){this.stopHeartbeat();const e=this.config?.heartbeatInterval??25e3;this.heartbeatTimer=globalThis.setInterval(()=>{if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return;const e={type:"PING",timestamp:Date.now()};this.send(e)},e)}stopHeartbeat(){null!==this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void console.warn("[SharedWorker] WebSocket 未连接,无法发送消息");const t="string"==typeof e?e:JSON.stringify(e);this.socket.send(t)}handleIncoming(t){if(!t)return;let s;try{s=JSON.parse(t)}catch{return void console.warn("[SharedWorker] 无法解析消息",t)}if(!s?.type)return;console.log(`[SharedWorker] 📨 收到服务器消息, type: ${s.type}`,s);const r={data:t,message:s};let o=0;for(const t of this.tabs.values())console.log(`[SharedWorker] 检查标签页 ${t.tabId}, 注册的类型:`,Array.from(t.registeredTypes)),t.registeredTypes.has(s.type)&&(console.log(`[SharedWorker] ✅ 发送消息到标签页 ${t.tabId}, type: ${s.type}`),this.sendToTab(t.port,e,r),o++);console.log(`[SharedWorker] 消息分发完成, type: ${s.type}, 发送给 ${o} 个标签页`)}registerCallback(e,t){const s=this.tabs.get(e);s?(s.registeredTypes.add(t.type),s.callbackMap.set(t.callbackId,t.type),console.log(`[SharedWorker] ✅ 标签页 ${e} 注册回调: ${t.type} (${t.callbackId})`),console.log(`[SharedWorker] 标签页 ${e} 当前注册的所有类型:`,Array.from(s.registeredTypes))):console.warn(`[SharedWorker] ⚠️ 标签页不存在: ${e}`)}unregisterCallback(e,t){const s=this.tabs.get(e);if(s)if(t.callbackId){const r=s.callbackMap.get(t.callbackId);if(r){s.callbackMap.delete(t.callbackId);Array.from(s.callbackMap.values()).some(e=>e===r)||s.registeredTypes.delete(r),console.log(`[SharedWorker] 标签页 ${e} 取消注册回调: ${r}`)}}else{s.registeredTypes.delete(t.type);for(const[e,r]of s.callbackMap.entries())r===t.type&&s.callbackMap.delete(e);console.log(`[SharedWorker] 标签页 ${e} 取消注册所有 ${t.type} 类型回调`)}else console.warn(`[SharedWorker] 标签页不存在: ${e}`)}sendToTab(e,t,s){const r={type:t,payload:s,timestamp:Date.now()};try{e.postMessage(r)}catch(e){console.error("[SharedWorker] 发送消息到标签页失败",e)}}broadcastToAllTabs(e,t){for(const s of this.tabs.values())this.sendToTab(s.port,e,t)}};globalThis.onconnect=e=>{const t=e.ports[0];t.onmessage=e=>{const s=e.data;switch(console.log(`[SharedWorker] 📬 收到标签页消息, type: ${s.type}, tabId: ${s.tabId}`),s.type){case"TAB_INIT":a.addTab(t,s);break;case"TAB_SEND":a.send(s.payload.data);break;case"TAB_VISIBILITY":a.updateTabVisibility(s.tabId,s.payload.isVisible);break;case"TAB_REGISTER_CALLBACK":console.log("[SharedWorker] 🔔 处理注册回调请求:",s.payload),a.registerCallback(s.tabId,s.payload);break;case"TAB_UNREGISTER_CALLBACK":a.unregisterCallback(s.tabId,s.payload);break;case"TAB_DISCONNECT":a.removeTab(s.tabId);break;case"TAB_PING":t.postMessage({type:"WORKER_PONG",timestamp:Date.now()});break;default:console.warn("[SharedWorker] 未知消息类型",s.type)}},t.start()};
1
+ "use strict";const e="WORKER_MESSAGE",t="WORKER_CONNECTED",s="WORKER_DISCONNECTED",r="WORKER_ERROR",o="WORKER_AUTH_CONFLICT";const a=new class{constructor(){this.tabs=new Map,this.socket=null,this.lastMessageByType=new Map,this.tabCleanupTimer=null,this.idleTimer=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.reconnectAttempts=0,this.manualClose=!1,this.currentUrl=null,this.currentUserId=null,this.currentToken=null,this.currentBaseUrl=null,this.lastOpenAt=0,this.fastClose1000Count=0,this.reconnectSuppressedUntil=0,this.config=null,this.sharedWorkerIdleTimeout=3e4,this.tabStaleTimeout=45e3}hasVisibleTab(){return Array.from(this.tabs.values()).some(e=>e.isVisible)}clearReconnectTimer(){null!==this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null)}startTabCleanup(){null===this.tabCleanupTimer&&(this.tabCleanupTimer=globalThis.setInterval(()=>{const e=Date.now(),t=[];for(const s of this.tabs.values())e-s.lastSeen>this.tabStaleTimeout&&t.push(s.tabId);t.length>0&&(console.warn("[SharedWorker] 检测到过期标签页,将清理:",t),t.forEach(e=>this.removeTab(e)))},15e3))}stopTabCleanup(){null!==this.tabCleanupTimer&&(clearInterval(this.tabCleanupTimer),this.tabCleanupTimer=null)}addTab(e,s){const{tabId:r,payload:a}=s,n=a;if(this.currentUserId&&this.currentUserId!==n.userId){const t={currentUserId:this.currentUserId,newUserId:n.userId,message:`检测到不同用户身份:当前连接用户为 ${this.currentUserId},新标签页尝试使用用户 ${n.userId} 连接。将复用现有连接。`};this.sendToTab(e,o,t),console.warn("[SharedWorker]",t.message)}const l={port:e,tabId:r,isVisible:n.isVisible,lastSeen:Date.now(),registeredTypes:new Set,callbackMap:new Map};this.tabs.set(r,l),console.log(`[SharedWorker] 标签页已添加: ${r}, 当前标签页数量: ${this.tabs.size}`),this.startTabCleanup();const i=n.url,c=n.userId,h=n.token,d=`${i}/${c}?token=${encodeURIComponent(h)}`,u=null!==this.currentBaseUrl||null!==this.currentUserId||null!==this.currentToken||null!==this.currentUrl,b=null!==this.currentBaseUrl&&this.currentBaseUrl!==i||null!==this.currentUserId&&this.currentUserId!==c||null!==this.currentToken&&this.currentToken!==h||null!==this.currentUrl&&this.currentUrl!==d;if(!u||b){if(u){const e={currentUserId:this.currentUserId??"",newUserId:c,message:`检测到连接身份/参数变化:oldUser=${this.currentUserId??"null"} -> newUser=${c}, oldBaseUrl=${this.currentBaseUrl??"null"} -> newBaseUrl=${i}, tokenChanged=${!this.currentToken||this.currentToken!==h}。将切换到最新登录态并重建连接。`};this.broadcastToAllTabs(o,e),console.warn("[SharedWorker]",e.message)}this.currentBaseUrl=i,this.currentUserId=c,this.currentToken=h,this.currentUrl=d,this.config=n.config,this.sharedWorkerIdleTimeout=n.sharedWorkerIdleTimeout??3e4,this.lastMessageByType.clear(),this.socket&&(console.log("[SharedWorker] 检测到登录态变化,断开旧连接以使用新参数"),this.disconnect())}else this.config=n.config,this.sharedWorkerIdleTimeout=n.sharedWorkerIdleTimeout??this.sharedWorkerIdleTimeout;this.socket&&this.socket.readyState===WebSocket.OPEN&&this.sendToTab(e,t,{}),this.checkAllTabsVisibility()}removeTab(e){this.tabs.delete(e),console.log(`[SharedWorker] 标签页已移除: ${e}, 剩余标签页数量: ${this.tabs.size}`),0===this.tabs.size?(console.log(`[SharedWorker] 所有标签页已关闭,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`),this.clearReconnectTimer(),this.stopTabCleanup(),this.startIdleTimer()):this.checkAllTabsVisibility()}updateTabVisibility(e,t){const s=this.tabs.get(e);s?(s.isVisible=t,s.lastSeen=Date.now(),console.log(`[SharedWorker] 标签页 ${e} 可见性更新: ${t}`),this.checkAllTabsVisibility()):console.warn(`[SharedWorker] 标签页不存在: ${e}`)}checkAllTabsVisibility(){if(0===this.tabs.size)return;Array.from(this.tabs.values()).every(e=>!e.isVisible)?(console.log(`[SharedWorker] 所有标签页都不可见,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`),this.clearReconnectTimer(),this.startIdleTimer()):(console.log("[SharedWorker] 至少有一个标签页可见,保持连接"),this.resetIdleTimer(),this.socket&&this.socket.readyState===WebSocket.OPEN||this.currentUrl&&(console.log("[SharedWorker] 检测到连接已断开,标签页可见,尝试重新连接"),this.connect()))}startIdleTimer(){this.clearIdleTimer(),this.idleTimer=globalThis.setTimeout(()=>{console.log("[SharedWorker] 空闲超时,断开连接"),this.disconnect()},this.sharedWorkerIdleTimeout)}resetIdleTimer(){this.clearIdleTimer()}clearIdleTimer(){null!==this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}connect(){if(this.currentUrl)if(Date.now()<this.reconnectSuppressedUntil)console.warn("[SharedWorker] 自动重连已熔断,暂不连接",{suppressedUntil:this.reconnectSuppressedUntil});else if(this.hasVisibleTab()){if(!this.socket||this.socket.readyState!==WebSocket.OPEN&&this.socket.readyState!==WebSocket.CONNECTING){this.manualClose=!1,this.clearReconnectTimer();try{this.socket=new WebSocket(this.currentUrl),this.socket.onopen=()=>{console.log("[SharedWorker] ✅ WebSocket 连接成功"),this.reconnectAttempts=0,this.lastOpenAt=Date.now(),this.fastClose1000Count=0,this.startHeartbeat(),console.log(`[SharedWorker] 通知 ${this.tabs.size} 个标签页: 已连接`),this.broadcastToAllTabs(t,{})},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{const t=Date.now(),o=this.lastOpenAt?t-this.lastOpenAt:-1;if(console.log("[SharedWorker] WebSocket 连接关闭",{code:e.code,reason:e.reason,wasClean:e.wasClean,liveMs:o}),this.stopHeartbeat(),this.broadcastToAllTabs(s,{}),1e3===e.code&&o>=0&&o<3e3){if(this.fastClose1000Count+=1,console.warn("[SharedWorker] 检测到快速 1000 关闭",{fastClose1000Count:this.fastClose1000Count,liveMs:o}),this.fastClose1000Count>=3){this.reconnectSuppressedUntil=t+6e4;const s={message:"WebSocket 被服务端频繁正常关闭(1000),已临时暂停自动重连 60s。请检查 token/服务端是否限制同账号多连接/是否需要额外鉴权消息。",error:{code:e.code,reason:e.reason,liveMs:o}};return void this.broadcastToAllTabs(r,s)}}else this.fastClose1000Count=0;!this.manualClose&&this.config?.autoReconnect&&this.tabs.size>0&&this.hasVisibleTab()&&this.scheduleReconnect()},this.socket.onerror=e=>{console.error("[SharedWorker] WebSocket 连接错误",e),this.stopHeartbeat();const t={message:"WebSocket 连接错误",error:e};this.broadcastToAllTabs(r,t)}}catch(e){console.error("[SharedWorker] 创建 WebSocket 连接失败",e);const t={message:"创建 WebSocket 连接失败",error:e};this.broadcastToAllTabs(r,t),this.config?.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}}else console.log("[SharedWorker] 当前无可见标签页,跳过连接");else console.error("[SharedWorker] 缺少 WebSocket URL")}scheduleReconnect(){if(0===this.tabs.size||!this.hasVisibleTab())return;const e=this.config?.maxReconnectAttempts??10,t=this.config?.reconnectDelay??3e3,s=this.config?.reconnectDelayMax??1e4;if(this.reconnectAttempts>=e||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=e&&console.warn("[SharedWorker] 已达到最大重连次数"));this.reconnectAttempts+=1;const r=Math.min(t*this.reconnectAttempts,s);console.log(`[SharedWorker] 将在 ${r}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.clearReconnectTimer(),this.reconnectTimer=globalThis.setTimeout(()=>{this.connect()},r)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.clearIdleTimer(),this.clearReconnectTimer(),this.socket&&(this.socket.close(),this.socket=null),this.reconnectAttempts=0}startHeartbeat(){this.stopHeartbeat();const e=this.config?.heartbeatInterval??25e3;this.heartbeatTimer=globalThis.setInterval(()=>{if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return;const e={type:"PING",timestamp:Date.now()};this.send(e)},e)}stopHeartbeat(){null!==this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void console.warn("[SharedWorker] WebSocket 未连接,无法发送消息");const t="string"==typeof e?e:JSON.stringify(e);this.socket.send(t)}handleIncoming(t){if(!t)return;let s;try{s=JSON.parse(t)}catch{return void console.warn("[SharedWorker] 无法解析消息",t)}if(!s?.type)return;console.log(`[SharedWorker] 📨 收到服务器消息, type: ${s.type}`,s);const r={data:t,message:s};this.lastMessageByType.set(s.type,r);let o=0;for(const t of this.tabs.values())console.log(`[SharedWorker] 检查标签页 ${t.tabId}, 注册的类型:`,Array.from(t.registeredTypes)),t.registeredTypes.has(s.type)&&(console.log(`[SharedWorker] ✅ 发送消息到标签页 ${t.tabId}, type: ${s.type}`),this.sendToTab(t.port,e,r),o++);console.log(`[SharedWorker] 消息分发完成, type: ${s.type}, 发送给 ${o} 个标签页`)}registerCallback(t,s){const r=this.tabs.get(t);if(!r)return void console.warn(`[SharedWorker] ⚠️ 标签页不存在: ${t}`);r.lastSeen=Date.now(),r.registeredTypes.add(s.type),r.callbackMap.set(s.callbackId,s.type),console.log(`[SharedWorker] ✅ 标签页 ${t} 注册回调: ${s.type} (${s.callbackId})`),console.log(`[SharedWorker] 标签页 ${t} 当前注册的所有类型:`,Array.from(r.registeredTypes));const o=this.lastMessageByType.get(s.type);o&&(console.log(`[SharedWorker] 🔁 回放缓存消息到标签页 ${t}, type: ${s.type}`),this.sendToTab(r.port,e,o))}unregisterCallback(e,t){const s=this.tabs.get(e);if(s)if(s.lastSeen=Date.now(),t.callbackId){const r=s.callbackMap.get(t.callbackId);if(r){s.callbackMap.delete(t.callbackId);Array.from(s.callbackMap.values()).some(e=>e===r)||s.registeredTypes.delete(r),console.log(`[SharedWorker] 标签页 ${e} 取消注册回调: ${r}`)}}else{s.registeredTypes.delete(t.type);for(const[e,r]of s.callbackMap.entries())r===t.type&&s.callbackMap.delete(e);console.log(`[SharedWorker] 标签页 ${e} 取消注册所有 ${t.type} 类型回调`)}else console.warn(`[SharedWorker] 标签页不存在: ${e}`)}sendToTab(e,t,s){const r={type:t,payload:s,timestamp:Date.now()};try{e.postMessage(r)}catch(e){console.error("[SharedWorker] 发送消息到标签页失败",e)}}broadcastToAllTabs(e,t){for(const s of this.tabs.values())this.sendToTab(s.port,e,t)}forceReset(e){console.warn("[SharedWorker] 🔄 收到强制重置指令,正在重置 Worker 状态",{reason:e}),this.disconnect(),this.lastMessageByType.clear(),this.currentUrl=null,this.currentBaseUrl=null,this.currentUserId=null,this.currentToken=null,this.broadcastToAllTabs(s,{}),console.log("[SharedWorker] ✅ Worker 状态已重置,等待新的连接参数")}forceShutdown(e){console.warn("[SharedWorker] ⚠️ 收到强制关闭指令,正在关闭 Worker",{reason:e}),this.disconnect(),this.lastMessageByType.clear(),this.currentUrl=null,this.currentBaseUrl=null,this.currentUserId=null,this.currentToken=null;for(const e of this.tabs.values()){try{e.port.postMessage({type:s,payload:{},timestamp:Date.now()})}catch{}try{e.port.close()}catch{}}this.tabs.clear(),this.stopTabCleanup();const t=globalThis.close;if("function"==typeof t){console.warn("[SharedWorker] 🛑 正在终止 SharedWorker 进程");try{t()}catch(e){console.warn("[SharedWorker] 终止 SharedWorker 失败",e)}}}handleNetworkOnline(){console.log("[SharedWorker] 🌐 收到网络恢复通知"),this.socket&&this.socket.readyState===WebSocket.OPEN?console.log("[SharedWorker] 已有活跃连接,无需重连"):(this.reconnectAttempts=0,this.clearReconnectTimer(),this.reconnectSuppressedUntil=0,this.currentUrl&&this.hasVisibleTab()?(console.log("[SharedWorker] 网络恢复,立即尝试重连"),this.connect()):console.log("[SharedWorker] 网络恢复,但无可见标签页或无 URL,等待条件满足"))}};globalThis.onconnect=e=>{const t=e.ports[0];t.onmessage=e=>{const s=e.data;console.log(`[SharedWorker] 📬 收到标签页消息, type: ${s.type}, tabId: ${s.tabId}`);const r=a.tabs?.get?.(s.tabId);switch(r&&(r.lastSeen=Date.now()),s.type){case"TAB_INIT":a.addTab(t,s);break;case"TAB_SEND":a.send(s.payload.data);break;case"TAB_VISIBILITY":a.updateTabVisibility(s.tabId,s.payload.isVisible);break;case"TAB_REGISTER_CALLBACK":console.log("[SharedWorker] 🔔 处理注册回调请求:",s.payload),a.registerCallback(s.tabId,s.payload);break;case"TAB_UNREGISTER_CALLBACK":a.unregisterCallback(s.tabId,s.payload);break;case"TAB_DISCONNECT":a.removeTab(s.tabId);break;case"TAB_PING":t.postMessage({type:"WORKER_PONG",timestamp:Date.now()});break;case"TAB_FORCE_RESET":a.forceReset(s.payload?.reason);break;case"TAB_FORCE_SHUTDOWN":a.forceShutdown(s.payload?.reason);break;case"TAB_NETWORK_ONLINE":a.handleNetworkOnline();break;default:console.warn("[SharedWorker] 未知消息类型",s.type)}},t.start()};
2
2
  //# sourceMappingURL=worker-script.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"worker-script.cjs.js","sources":["../src/worker-script.ts"],"sourcesContent":["/**\n * SharedWorker 脚本\n * 管理跨标签页共享的 WebSocket 连接\n * 注意:此文件会被内联为 Blob,不能有任何 import 语句\n */\n\n// ============ 类型定义(复制自 types.ts,避免 import) ============\n\n// 使用普通对象,不使用 TypeScript 语法\nconst 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}\n\n// TypeScript 类型定义(编译后会被移除)\ntype WorkerToTabMessageTypeValue = (typeof WorkerToTabMessageType)[keyof typeof WorkerToTabMessageType]\n\ninterface TabToWorkerMessage {\n type: string\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n payload?: any\n tabId: string\n timestamp: number\n}\n\ninterface WorkerToTabMessage {\n type: WorkerToTabMessageTypeValue | string\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n payload?: any\n timestamp: number\n}\n\ninterface TabInfo {\n port: MessagePort\n tabId: string\n isVisible: boolean\n registeredTypes: Set<string>\n callbackMap: Map<string, string>\n}\n\ninterface InitPayload {\n url: string\n userId: string\n token: string\n isVisible: boolean\n config: {\n heartbeatInterval?: number\n maxReconnectAttempts?: number\n reconnectDelay?: number\n reconnectDelayMax?: number\n autoReconnect?: boolean\n logLevel?: string\n }\n sharedWorkerIdleTimeout?: number\n}\n\ninterface SendPayload {\n data: string | object\n}\n\ninterface VisibilityPayload {\n isVisible: boolean\n}\n\ninterface RegisterCallbackPayload {\n type: string\n callbackId: string\n}\n\ninterface UnregisterCallbackPayload {\n type: string\n callbackId?: string\n}\n\ninterface ServerMessagePayload {\n data: string\n message: {\n type: string\n data: unknown\n meta?: Record<string, unknown>\n timestamp?: number\n }\n}\n\ninterface ErrorPayload {\n message: string\n error?: unknown\n}\n\ninterface AuthConflictPayload {\n currentUserId: string\n newUserId: string\n message: string\n}\n\n// ============ WebSocket 管理器 ============\n\n/**\n * WebSocket 管理器\n * 负责管理唯一的 WebSocket 连接和所有标签页\n */\nclass WebSocketManager {\n /** 标签页列表 (tabId -> TabInfo) */\n private tabs: Map<string, TabInfo> = new Map()\n\n /** WebSocket 连接实例 */\n private socket: WebSocket | null = null\n\n /** 空闲定时器 */\n private idleTimer: ReturnType<typeof setTimeout> | null = null\n\n /** 心跳定时器 */\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null\n\n /** 重连定时器 */\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null\n\n /** 重连次数 */\n private reconnectAttempts = 0\n\n /** 是否手动关闭 */\n private manualClose = false\n\n /** 当前连接的 URL */\n private currentUrl: string | null = null\n\n /** 当前用户ID */\n private currentUserId: string | null = null\n\n /** 配置 */\n private config: InitPayload['config'] | null = null\n\n /** SharedWorker 空闲超时时间(毫秒) */\n private sharedWorkerIdleTimeout = 30000\n\n /**\n * 添加标签页\n */\n addTab(port: MessagePort, message: TabToWorkerMessage): void {\n const { tabId, payload } = message\n const initPayload = payload as InitPayload\n\n // 检查身份冲突\n if (this.currentUserId && this.currentUserId !== initPayload.userId) {\n const conflictPayload: AuthConflictPayload = {\n currentUserId: this.currentUserId,\n newUserId: initPayload.userId,\n message: `检测到不同用户身份:当前连接用户为 ${this.currentUserId},新标签页尝试使用用户 ${initPayload.userId} 连接。将复用现有连接。`,\n }\n\n this.sendToTab(port, WorkerToTabMessageType.WORKER_AUTH_CONFLICT, conflictPayload)\n console.warn('[SharedWorker]', conflictPayload.message)\n }\n\n // 添加标签页信息\n const tabInfo: TabInfo = {\n port,\n tabId,\n isVisible: initPayload.isVisible,\n registeredTypes: new Set(),\n callbackMap: new Map(),\n }\n\n this.tabs.set(tabId, tabInfo)\n console.log(`[SharedWorker] 标签页已添加: ${tabId}, 当前标签页数量: ${this.tabs.size}`)\n\n // 如果还没有连接,创建连接\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\n this.connect()\n } else {\n // 已有连接,直接通知标签页已连接\n this.sendToTab(port, WorkerToTabMessageType.WORKER_CONNECTED, {})\n }\n\n // 重置空闲定时器\n this.resetIdleTimer()\n }\n\n /**\n * 移除标签页\n */\n removeTab(tabId: string): void {\n this.tabs.delete(tabId)\n console.log(`[SharedWorker] 标签页已移除: ${tabId}, 剩余标签页数量: ${this.tabs.size}`)\n\n if (this.tabs.size === 0) {\n // 没有标签页了,开始空闲倒计时\n console.log(`[SharedWorker] 所有标签页已关闭,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`)\n this.startIdleTimer()\n } else {\n // 还有标签页,检查可见性\n this.checkAllTabsVisibility()\n }\n }\n\n /**\n * 更新标签页可见性\n */\n updateTabVisibility(tabId: string, isVisible: boolean): void {\n const tab = this.tabs.get(tabId)\n if (!tab) {\n console.warn(`[SharedWorker] 标签页不存在: ${tabId}`)\n return\n }\n\n tab.isVisible = isVisible\n console.log(`[SharedWorker] 标签页 ${tabId} 可见性更新: ${isVisible}`)\n\n this.checkAllTabsVisibility()\n }\n\n /**\n * 检查所有标签页可见性\n */\n private checkAllTabsVisibility(): void {\n if (this.tabs.size === 0) return\n\n const allHidden = Array.from(this.tabs.values()).every((tab) => !tab.isVisible)\n\n if (allHidden) {\n // 所有标签页都不可见,开始空闲倒计时\n console.log(`[SharedWorker] 所有标签页都不可见,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`)\n this.startIdleTimer()\n } else {\n // 至少有一个标签页可见,取消倒计时\n console.log('[SharedWorker] 至少有一个标签页可见,保持连接')\n this.resetIdleTimer()\n }\n }\n\n /**\n * 开始空闲定时器\n */\n private startIdleTimer(): void {\n this.clearIdleTimer()\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.idleTimer = (globalThis as any).setTimeout(() => {\n console.log('[SharedWorker] 空闲超时,断开连接')\n this.disconnect()\n }, this.sharedWorkerIdleTimeout)\n }\n\n /**\n * 重置空闲定时器\n */\n private resetIdleTimer(): void {\n this.clearIdleTimer()\n }\n\n /**\n * 清除空闲定时器\n */\n private clearIdleTimer(): void {\n if (this.idleTimer !== null) {\n clearTimeout(this.idleTimer)\n this.idleTimer = null\n }\n }\n\n /**\n * 连接到 WebSocket 服务器\n */\n private connect(): void {\n if (!this.currentUrl) {\n console.error('[SharedWorker] 缺少 WebSocket URL')\n return\n }\n\n this.manualClose = false\n\n try {\n this.socket = new WebSocket(this.currentUrl)\n\n this.socket.onopen = () => {\n console.log('[SharedWorker] ✅ WebSocket 连接成功')\n this.reconnectAttempts = 0\n this.startHeartbeat()\n\n // 通知所有标签页已连接\n console.log(`[SharedWorker] 通知 ${this.tabs.size} 个标签页: 已连接`)\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_CONNECTED, {})\n }\n\n this.socket.onmessage = (event: MessageEvent) => {\n this.handleIncoming(event.data)\n }\n\n this.socket.onclose = (event: CloseEvent) => {\n console.log('[SharedWorker] WebSocket 连接关闭', event.code, event.reason)\n this.stopHeartbeat()\n\n // 通知所有标签页已断开\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_DISCONNECTED, {})\n\n if (!this.manualClose && this.config?.autoReconnect) {\n this.scheduleReconnect()\n }\n }\n\n this.socket.onerror = (event: Event) => {\n console.error('[SharedWorker] WebSocket 连接错误', event)\n this.stopHeartbeat()\n\n const errorPayload: ErrorPayload = {\n message: 'WebSocket 连接错误',\n error: event,\n }\n\n // 通知所有标签页发生错误\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload)\n }\n } catch (error) {\n console.error('[SharedWorker] 创建 WebSocket 连接失败', error)\n\n const errorPayload: ErrorPayload = {\n message: '创建 WebSocket 连接失败',\n error,\n }\n\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload)\n\n if (this.config?.autoReconnect && !this.manualClose) {\n this.scheduleReconnect()\n }\n }\n }\n\n /**\n * 计划重连\n */\n private scheduleReconnect(): void {\n const maxAttempts = this.config?.maxReconnectAttempts ?? 10\n const reconnectDelay = this.config?.reconnectDelay ?? 3000\n const reconnectDelayMax = this.config?.reconnectDelayMax ?? 10000\n\n if (this.reconnectAttempts >= maxAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= maxAttempts) {\n console.warn('[SharedWorker] 已达到最大重连次数')\n }\n return\n }\n\n this.reconnectAttempts += 1\n const delay = Math.min(reconnectDelay * this.reconnectAttempts, reconnectDelayMax)\n\n console.log(`[SharedWorker] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`)\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.reconnectTimer = (globalThis as any).setTimeout(() => {\n this.connect()\n }, delay)\n }\n\n /**\n * 断开连接\n */\n private disconnect(): void {\n this.manualClose = true\n this.stopHeartbeat()\n this.clearIdleTimer()\n\n if (this.reconnectTimer !== null) {\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.reconnectAttempts = 0\n }\n\n /**\n * 启动心跳\n */\n private startHeartbeat(): void {\n this.stopHeartbeat()\n\n const heartbeatInterval = this.config?.heartbeatInterval ?? 25000\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.heartbeatTimer = (globalThis as any).setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n // 默认心跳消息\n const heartbeatData = { type: 'PING', timestamp: Date.now() }\n this.send(heartbeatData)\n }, heartbeatInterval)\n }\n\n /**\n * 停止心跳\n */\n private stopHeartbeat(): void {\n if (this.heartbeatTimer !== null) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n /**\n * 发送消息到服务器\n */\n send(data: string | object): void {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n console.warn('[SharedWorker] 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 */\n private handleIncoming(data: string): void {\n if (!data) return\n\n let message: { type: string; data: unknown; meta?: Record<string, unknown>; timestamp?: number }\n try {\n message = JSON.parse(data)\n } catch {\n console.warn('[SharedWorker] 无法解析消息', data)\n return\n }\n\n if (!message?.type) {\n return\n }\n\n console.log(`[SharedWorker] 📨 收到服务器消息, type: ${message.type}`, message)\n\n // 分发消息到所有注册了该类型的标签页\n const serverMessagePayload: ServerMessagePayload = {\n data,\n message,\n }\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\n console.log(`[SharedWorker] 消息分发完成, type: ${message.type}, 发送给 ${sentCount} 个标签页`)\n }\n\n /**\n * 注册回调\n */\n registerCallback(tabId: string, payload: RegisterCallbackPayload): void {\n const tab = this.tabs.get(tabId)\n if (!tab) {\n console.warn(`[SharedWorker] ⚠️ 标签页不存在: ${tabId}`)\n return\n }\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\n /**\n * 取消注册回调\n */\n unregisterCallback(tabId: string, payload: UnregisterCallbackPayload): void {\n const tab = this.tabs.get(tabId)\n if (!tab) {\n console.warn(`[SharedWorker] 标签页不存在: ${tabId}`)\n return\n }\n\n if (payload.callbackId) {\n // 移除特定回调\n const type = tab.callbackMap.get(payload.callbackId)\n if (type) {\n tab.callbackMap.delete(payload.callbackId)\n\n // 检查是否还有其他回调注册了该类型\n const hasOtherCallbacks = Array.from(tab.callbackMap.values()).some((t) => t === type)\n if (!hasOtherCallbacks) {\n tab.registeredTypes.delete(type)\n }\n\n console.log(`[SharedWorker] 标签页 ${tabId} 取消注册回调: ${type}`)\n }\n } else {\n // 移除该类型的所有回调\n tab.registeredTypes.delete(payload.type)\n\n // 移除 callbackMap 中该类型的所有条目\n for (const [callbackId, type] of tab.callbackMap.entries()) {\n if (type === payload.type) {\n tab.callbackMap.delete(callbackId)\n }\n }\n\n console.log(`[SharedWorker] 标签页 ${tabId} 取消注册所有 ${payload.type} 类型回调`)\n }\n }\n\n /**\n * 发送消息到特定标签页\n */\n private sendToTab(port: MessagePort, type: WorkerToTabMessageTypeValue | string, payload: unknown): void {\n const message: WorkerToTabMessage = {\n type,\n payload,\n timestamp: Date.now(),\n }\n\n try {\n port.postMessage(message)\n } catch (error) {\n console.error('[SharedWorker] 发送消息到标签页失败', error)\n }\n }\n\n /**\n * 广播消息到所有标签页\n */\n private broadcastToAllTabs(type: WorkerToTabMessageTypeValue | string, payload: unknown): void {\n for (const tab of this.tabs.values()) {\n this.sendToTab(tab.port, type, payload)\n }\n }\n}\n\n// 创建全局 WebSocket 管理器实例\nconst wsManager = new WebSocketManager()\n\n/**\n * SharedWorker 连接事件监听\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n;(globalThis as any).onconnect = (event: MessageEvent) => {\n const port = event.ports[0]\n\n port.onmessage = (e: MessageEvent) => {\n const message = e.data as TabToWorkerMessage\n console.log(`[SharedWorker] 📬 收到标签页消息, type: ${message.type}, tabId: ${message.tabId}`)\n\n switch (message.type) {\n case 'TAB_INIT':\n wsManager.addTab(port, message)\n break\n\n case 'TAB_SEND':\n wsManager.send((message.payload as SendPayload).data)\n break\n\n case 'TAB_VISIBILITY':\n wsManager.updateTabVisibility(message.tabId, (message.payload as VisibilityPayload).isVisible)\n break\n\n case 'TAB_REGISTER_CALLBACK':\n console.log(`[SharedWorker] 🔔 处理注册回调请求:`, message.payload)\n wsManager.registerCallback(message.tabId, message.payload as RegisterCallbackPayload)\n break\n\n case 'TAB_UNREGISTER_CALLBACK':\n wsManager.unregisterCallback(message.tabId, message.payload as UnregisterCallbackPayload)\n break\n\n case 'TAB_DISCONNECT':\n wsManager.removeTab(message.tabId)\n break\n\n case 'TAB_PING':\n // 响应 PING\n port.postMessage({\n type: 'WORKER_PONG',\n timestamp: Date.now(),\n })\n break\n\n default:\n console.warn('[SharedWorker] 未知消息类型', message.type)\n }\n }\n\n port.start()\n}\n"],"names":["WorkerToTabMessageType","wsManager","constructor","this","tabs","Map","socket","idleTimer","heartbeatTimer","reconnectTimer","reconnectAttempts","manualClose","currentUrl","currentUserId","config","sharedWorkerIdleTimeout","addTab","port","message","tabId","payload","initPayload","userId","conflictPayload","newUserId","sendToTab","console","warn","tabInfo","isVisible","registeredTypes","Set","callbackMap","set","log","size","readyState","WebSocket","OPEN","url","encodeURIComponent","token","connect","resetIdleTimer","removeTab","delete","startIdleTimer","checkAllTabsVisibility","updateTabVisibility","tab","get","Array","from","values","every","clearIdleTimer","globalThis","setTimeout","disconnect","clearTimeout","onopen","startHeartbeat","broadcastToAllTabs","onmessage","event","handleIncoming","data","onclose","code","reason","stopHeartbeat","autoReconnect","scheduleReconnect","onerror","error","errorPayload","maxAttempts","maxReconnectAttempts","reconnectDelay","reconnectDelayMax","delay","Math","min","close","heartbeatInterval","setInterval","heartbeatData","type","timestamp","Date","now","send","clearInterval","JSON","stringify","parse","serverMessagePayload","sentCount","has","registerCallback","add","callbackId","unregisterCallback","some","t","entries","postMessage","onconnect","ports","e","start"],"mappings":"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"}
1
+ {"version":3,"file":"worker-script.cjs.js","sources":["../src/worker-script.ts"],"sourcesContent":["/**\n * SharedWorker 脚本\n * 管理跨标签页共享的 WebSocket 连接\n * 注意:此文件会被内联为 Blob,不能有任何 import 语句\n */\n\n// ============ 类型定义(复制自 types.ts,避免 import) ============\n\n// 使用普通对象,不使用 TypeScript 语法\nconst 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}\n\n// TypeScript 类型定义(编译后会被移除)\ntype WorkerToTabMessageTypeValue = (typeof WorkerToTabMessageType)[keyof typeof WorkerToTabMessageType]\n\ninterface TabToWorkerMessage {\n type: string\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n payload?: any\n tabId: string\n timestamp: number\n}\n\ninterface WorkerToTabMessage {\n type: WorkerToTabMessageTypeValue | string\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n payload?: any\n timestamp: number\n}\n\ninterface TabInfo {\n port: MessagePort\n tabId: string\n isVisible: boolean\n /** 最近一次收到该标签页心跳/消息的时间戳 */\n lastSeen: number\n registeredTypes: Set<string>\n callbackMap: Map<string, string>\n}\n\ninterface InitPayload {\n url: string\n userId: string\n token: string\n isVisible: boolean\n config: {\n heartbeatInterval?: number\n maxReconnectAttempts?: number\n reconnectDelay?: number\n reconnectDelayMax?: number\n autoReconnect?: boolean\n logLevel?: string\n }\n sharedWorkerIdleTimeout?: number\n}\n\ninterface ForceShutdownPayload {\n reason?: string\n}\n\ninterface SendPayload {\n data: string | object\n}\n\ninterface VisibilityPayload {\n isVisible: boolean\n}\n\ninterface RegisterCallbackPayload {\n type: string\n callbackId: string\n}\n\ninterface UnregisterCallbackPayload {\n type: string\n callbackId?: string\n}\n\ninterface ServerMessagePayload {\n data: string\n message: {\n type: string\n data: unknown\n meta?: Record<string, unknown>\n timestamp?: number\n }\n}\n\ninterface ErrorPayload {\n message: string\n error?: unknown\n}\n\ninterface AuthConflictPayload {\n currentUserId: string\n newUserId: string\n message: string\n}\n\n// ============ WebSocket 管理器 ============\n\n/**\n * WebSocket 管理器\n * 负责管理唯一的 WebSocket 连接和所有标签页\n */\nclass WebSocketManager {\n /** 标签页列表 (tabId -> TabInfo) */\n private tabs: Map<string, TabInfo> = new Map()\n\n /** WebSocket 连接实例 */\n private socket: WebSocket | null = null\n\n /** 最近一次服务器消息缓存(按 type) */\n private lastMessageByType: Map<string, ServerMessagePayload> = new Map()\n\n /** 标签页清理定时器(回收已关闭/崩溃的标签页) */\n private tabCleanupTimer: ReturnType<typeof setInterval> | null = null\n\n /** 空闲定时器 */\n private idleTimer: ReturnType<typeof setTimeout> | null = null\n\n /** 心跳定时器 */\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null\n\n /** 重连定时器 */\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null\n\n /** 重连次数 */\n private reconnectAttempts = 0\n\n /** 是否手动关闭 */\n private manualClose = false\n\n /** 当前连接的 URL */\n private currentUrl: string | null = null\n\n /** 当前用户ID */\n private currentUserId: string | null = null\n\n /** 当前 token(用于判断登录态变化) */\n private currentToken: string | null = null\n\n /** 当前 baseUrl(用于判断环境/域名变化) */\n private currentBaseUrl: string | null = null\n\n /** 上次连接打开时间 */\n private lastOpenAt = 0\n\n /** 连续快速被服务端正常关闭(1000)次数 */\n private fastClose1000Count = 0\n\n /** 自动重连熔断到期时间戳(ms) */\n private reconnectSuppressedUntil = 0\n\n /** 配置 */\n private config: InitPayload['config'] | null = null\n\n /** SharedWorker 空闲超时时间(毫秒) */\n private sharedWorkerIdleTimeout = 30000\n\n /** tab 超时时间(毫秒):超过该时间未心跳则认为已关闭 */\n private tabStaleTimeout = 45000\n\n /**\n * 是否存在可见标签页\n */\n private hasVisibleTab(): boolean {\n return Array.from(this.tabs.values()).some((tab) => tab.isVisible)\n }\n\n /**\n * 清理重连定时器\n */\n private clearReconnectTimer(): void {\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n }\n\n /**\n * 启动 tab 清理(兜底:页面强制关闭/崩溃未发送 TAB_DISCONNECT 时也能回收)\n */\n private startTabCleanup(): void {\n if (this.tabCleanupTimer !== null) return\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.tabCleanupTimer = (globalThis as any).setInterval(() => {\n const now = Date.now()\n const staleTabIds: string[] = []\n for (const tab of this.tabs.values()) {\n if (now - tab.lastSeen > this.tabStaleTimeout) {\n staleTabIds.push(tab.tabId)\n }\n }\n\n if (staleTabIds.length > 0) {\n console.warn('[SharedWorker] 检测到过期标签页,将清理:', staleTabIds)\n staleTabIds.forEach((id) => this.removeTab(id))\n }\n }, 15000)\n }\n\n private stopTabCleanup(): void {\n if (this.tabCleanupTimer !== null) {\n clearInterval(this.tabCleanupTimer)\n this.tabCleanupTimer = null\n }\n }\n\n /**\n * 添加标签页\n */\n addTab(port: MessagePort, message: TabToWorkerMessage): void {\n const { tabId, payload } = message\n const initPayload = payload as InitPayload\n\n // 检查身份冲突\n if (this.currentUserId && this.currentUserId !== initPayload.userId) {\n const conflictPayload: AuthConflictPayload = {\n currentUserId: this.currentUserId,\n newUserId: initPayload.userId,\n message: `检测到不同用户身份:当前连接用户为 ${this.currentUserId},新标签页尝试使用用户 ${initPayload.userId} 连接。将复用现有连接。`,\n }\n\n this.sendToTab(port, WorkerToTabMessageType.WORKER_AUTH_CONFLICT, conflictPayload)\n console.warn('[SharedWorker]', conflictPayload.message)\n }\n\n // 添加标签页信息\n const tabInfo: TabInfo = {\n port,\n tabId,\n isVisible: initPayload.isVisible,\n lastSeen: Date.now(),\n registeredTypes: new Set(),\n callbackMap: new Map(),\n }\n\n this.tabs.set(tabId, tabInfo)\n console.log(`[SharedWorker] 标签页已添加: ${tabId}, 当前标签页数量: ${this.tabs.size}`)\n this.startTabCleanup()\n\n // 计算本次期望连接参数(允许登录后 token 更新 / 切换账号)\n const nextBaseUrl = initPayload.url\n const nextUserId = initPayload.userId\n const nextToken = initPayload.token\n const nextUrl = `${nextBaseUrl}/${nextUserId}?token=${encodeURIComponent(nextToken)}`\n\n const hasExistingIdentity =\n this.currentBaseUrl !== null ||\n this.currentUserId !== null ||\n this.currentToken !== null ||\n this.currentUrl !== null\n const identityChanged =\n (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\n if (!hasExistingIdentity || identityChanged) {\n if (hasExistingIdentity) {\n const conflictPayload: AuthConflictPayload = {\n currentUserId: this.currentUserId ?? '',\n newUserId: nextUserId,\n message:\n `检测到连接身份/参数变化:` +\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 // 通知所有标签页:登录态变化(方便页面自行处理旧会话)\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_AUTH_CONFLICT, conflictPayload)\n console.warn('[SharedWorker]', conflictPayload.message)\n }\n\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\n // 登录态切换:清空缓存(避免新 tab 回放到旧数据)\n this.lastMessageByType.clear()\n\n // 断开旧 socket(如果有),由后续 checkAllTabsVisibility 决定是否立即用新参数连接\n if (this.socket) {\n console.log('[SharedWorker] 检测到登录态变化,断开旧连接以使用新参数')\n this.disconnect()\n }\n } else {\n // 未变化时,仅更新配置/超时时间(允许新 tab 提供更完整配置)\n this.config = initPayload.config\n this.sharedWorkerIdleTimeout = initPayload.sharedWorkerIdleTimeout ?? this.sharedWorkerIdleTimeout\n }\n\n // 若已有连接,直接通知该标签页\n if (this.socket && this.socket.readyState === WebSocket.OPEN) {\n this.sendToTab(port, WorkerToTabMessageType.WORKER_CONNECTED, {})\n }\n\n // 检查所有标签页可见性,决定是否需要保持连接/发起连接\n this.checkAllTabsVisibility()\n }\n\n /**\n * 移除标签页\n */\n removeTab(tabId: string): void {\n this.tabs.delete(tabId)\n console.log(`[SharedWorker] 标签页已移除: ${tabId}, 剩余标签页数量: ${this.tabs.size}`)\n\n if (this.tabs.size === 0) {\n // 没有标签页了,开始空闲倒计时\n console.log(`[SharedWorker] 所有标签页已关闭,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`)\n this.clearReconnectTimer()\n this.stopTabCleanup()\n this.startIdleTimer()\n } else {\n // 还有标签页,检查可见性\n this.checkAllTabsVisibility()\n }\n }\n\n /**\n * 更新标签页可见性\n */\n updateTabVisibility(tabId: string, isVisible: boolean): void {\n const tab = this.tabs.get(tabId)\n if (!tab) {\n console.warn(`[SharedWorker] 标签页不存在: ${tabId}`)\n return\n }\n\n tab.isVisible = isVisible\n tab.lastSeen = Date.now()\n console.log(`[SharedWorker] 标签页 ${tabId} 可见性更新: ${isVisible}`)\n\n this.checkAllTabsVisibility()\n }\n\n /**\n * 检查所有标签页可见性\n */\n private checkAllTabsVisibility(): void {\n if (this.tabs.size === 0) return\n\n const allHidden = Array.from(this.tabs.values()).every((tab) => !tab.isVisible)\n\n if (allHidden) {\n // 所有标签页都不可见,开始空闲倒计时\n console.log(`[SharedWorker] 所有标签页都不可见,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`)\n // 不要在后台持续重连(容易造成频繁断开/重连、也会影响新标签页复用)\n this.clearReconnectTimer()\n this.startIdleTimer()\n } else {\n // 至少有一个标签页可见,取消倒计时\n console.log('[SharedWorker] 至少有一个标签页可见,保持连接')\n this.resetIdleTimer()\n\n // 检查连接状态,如果未连接则重新连接\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\n /**\n * 开始空闲定时器\n */\n private startIdleTimer(): void {\n this.clearIdleTimer()\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.idleTimer = (globalThis as any).setTimeout(() => {\n console.log('[SharedWorker] 空闲超时,断开连接')\n this.disconnect()\n }, this.sharedWorkerIdleTimeout)\n }\n\n /**\n * 重置空闲定时器\n */\n private resetIdleTimer(): void {\n this.clearIdleTimer()\n }\n\n /**\n * 清除空闲定时器\n */\n private clearIdleTimer(): void {\n if (this.idleTimer !== null) {\n clearTimeout(this.idleTimer)\n this.idleTimer = null\n }\n }\n\n /**\n * 连接到 WebSocket 服务器\n */\n private connect(): void {\n if (!this.currentUrl) {\n console.error('[SharedWorker] 缺少 WebSocket URL')\n return\n }\n\n // 熔断:短时间内被服务端频繁正常关闭时,暂停自动重连,避免打爆服务端/刷屏\n if (Date.now() < this.reconnectSuppressedUntil) {\n console.warn('[SharedWorker] 自动重连已熔断,暂不连接', {\n suppressedUntil: this.reconnectSuppressedUntil,\n })\n return\n }\n\n // 没有可见标签页时,不主动连接(等 TAB_VISIBILITY 变为 true 再连)\n if (!this.hasVisibleTab()) {\n console.log('[SharedWorker] 当前无可见标签页,跳过连接')\n return\n }\n\n // 避免重复 connect\n if (this.socket && (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING)) {\n return\n }\n\n this.manualClose = false\n this.clearReconnectTimer()\n\n try {\n this.socket = new WebSocket(this.currentUrl)\n\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\n // 通知所有标签页已连接\n console.log(`[SharedWorker] 通知 ${this.tabs.size} 个标签页: 已连接`)\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_CONNECTED, {})\n }\n\n this.socket.onmessage = (event: MessageEvent) => {\n this.handleIncoming(event.data)\n }\n\n this.socket.onclose = (event: CloseEvent) => {\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 as any).wasClean,\n liveMs,\n })\n this.stopHeartbeat()\n\n // 通知所有标签页已断开\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_DISCONNECTED, {})\n\n // 诊断:服务端正常关闭(1000)且“快速关闭”,多半是服务端策略(鉴权失败/单连接踢线/频控等)\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\n // 连续快速关闭达到阈值:熔断 60s 并广播错误给页面,避免无限重连\n if (this.fastClose1000Count >= 3) {\n this.reconnectSuppressedUntil = now + 60000\n const errorPayload: ErrorPayload = {\n message:\n '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 } else {\n // 非快速 1000,重置计数\n this.fastClose1000Count = 0\n }\n\n // 仅在「有可见标签页」时才自动重连(避免后台空转重连)\n if (!this.manualClose && this.config?.autoReconnect && this.tabs.size > 0 && this.hasVisibleTab()) {\n this.scheduleReconnect()\n }\n }\n\n this.socket.onerror = (event: Event) => {\n console.error('[SharedWorker] WebSocket 连接错误', event)\n this.stopHeartbeat()\n\n const errorPayload: ErrorPayload = {\n message: 'WebSocket 连接错误',\n error: event,\n }\n\n // 通知所有标签页发生错误\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload)\n }\n } catch (error) {\n console.error('[SharedWorker] 创建 WebSocket 连接失败', error)\n\n const errorPayload: ErrorPayload = {\n message: '创建 WebSocket 连接失败',\n error,\n }\n\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload)\n\n if (this.config?.autoReconnect && !this.manualClose) {\n this.scheduleReconnect()\n }\n }\n }\n\n /**\n * 计划重连\n */\n private scheduleReconnect(): void {\n // 后台/无标签页时不重连,等待可见标签页触发 connect()\n if (this.tabs.size === 0 || !this.hasVisibleTab()) {\n return\n }\n\n const maxAttempts = this.config?.maxReconnectAttempts ?? 10\n const reconnectDelay = this.config?.reconnectDelay ?? 3000\n const reconnectDelayMax = this.config?.reconnectDelayMax ?? 10000\n\n if (this.reconnectAttempts >= maxAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= maxAttempts) {\n console.warn('[SharedWorker] 已达到最大重连次数')\n }\n return\n }\n\n this.reconnectAttempts += 1\n const delay = Math.min(reconnectDelay * this.reconnectAttempts, reconnectDelayMax)\n\n console.log(`[SharedWorker] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`)\n\n this.clearReconnectTimer()\n this.reconnectTimer = (globalThis as any).setTimeout(() => {\n this.connect()\n }, delay)\n }\n\n /**\n * 断开连接\n */\n private disconnect(): void {\n this.manualClose = true\n this.stopHeartbeat()\n this.clearIdleTimer()\n\n this.clearReconnectTimer()\n\n if (this.socket) {\n this.socket.close()\n this.socket = null\n }\n\n this.reconnectAttempts = 0\n }\n\n /**\n * 启动心跳\n */\n private startHeartbeat(): void {\n this.stopHeartbeat()\n\n const heartbeatInterval = this.config?.heartbeatInterval ?? 25000\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.heartbeatTimer = (globalThis as any).setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n // 默认心跳消息\n const heartbeatData = { type: 'PING', timestamp: Date.now() }\n this.send(heartbeatData)\n }, heartbeatInterval)\n }\n\n /**\n * 停止心跳\n */\n private stopHeartbeat(): void {\n if (this.heartbeatTimer !== null) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n /**\n * 发送消息到服务器\n */\n send(data: string | object): void {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n console.warn('[SharedWorker] 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 */\n private handleIncoming(data: string): void {\n if (!data) return\n\n let message: { type: string; data: unknown; meta?: Record<string, unknown>; timestamp?: number }\n try {\n message = JSON.parse(data)\n } catch {\n console.warn('[SharedWorker] 无法解析消息', data)\n return\n }\n\n if (!message?.type) {\n return\n }\n\n console.log(`[SharedWorker] 📨 收到服务器消息, type: ${message.type}`, message)\n\n // 分发消息到所有注册了该类型的标签页\n const serverMessagePayload: ServerMessagePayload = {\n data,\n message,\n }\n\n // 缓存该类型最后一条消息,便于新标签页/晚注册回调能立即拿到最新状态\n this.lastMessageByType.set(message.type, serverMessagePayload)\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\n console.log(`[SharedWorker] 消息分发完成, type: ${message.type}, 发送给 ${sentCount} 个标签页`)\n }\n\n /**\n * 注册回调\n */\n registerCallback(tabId: string, payload: RegisterCallbackPayload): void {\n const tab = this.tabs.get(tabId)\n if (!tab) {\n console.warn(`[SharedWorker] ⚠️ 标签页不存在: ${tabId}`)\n return\n }\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\n // 回放该类型的最后一条消息(如果有),确保新开标签页能立刻拿到最新数据\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\n /**\n * 取消注册回调\n */\n unregisterCallback(tabId: string, payload: UnregisterCallbackPayload): void {\n const tab = this.tabs.get(tabId)\n if (!tab) {\n console.warn(`[SharedWorker] 标签页不存在: ${tabId}`)\n return\n }\n\n tab.lastSeen = Date.now()\n if (payload.callbackId) {\n // 移除特定回调\n const type = tab.callbackMap.get(payload.callbackId)\n if (type) {\n tab.callbackMap.delete(payload.callbackId)\n\n // 检查是否还有其他回调注册了该类型\n const hasOtherCallbacks = Array.from(tab.callbackMap.values()).some((t) => t === type)\n if (!hasOtherCallbacks) {\n tab.registeredTypes.delete(type)\n }\n\n console.log(`[SharedWorker] 标签页 ${tabId} 取消注册回调: ${type}`)\n }\n } else {\n // 移除该类型的所有回调\n tab.registeredTypes.delete(payload.type)\n\n // 移除 callbackMap 中该类型的所有条目\n for (const [callbackId, type] of tab.callbackMap.entries()) {\n if (type === payload.type) {\n tab.callbackMap.delete(callbackId)\n }\n }\n\n console.log(`[SharedWorker] 标签页 ${tabId} 取消注册所有 ${payload.type} 类型回调`)\n }\n }\n\n /**\n * 发送消息到特定标签页\n */\n private sendToTab(port: MessagePort, type: WorkerToTabMessageTypeValue | string, payload: unknown): void {\n const message: WorkerToTabMessage = {\n type,\n payload,\n timestamp: Date.now(),\n }\n\n try {\n port.postMessage(message)\n } catch (error) {\n console.error('[SharedWorker] 发送消息到标签页失败', error)\n }\n }\n\n /**\n * 广播消息到所有标签页\n */\n private broadcastToAllTabs(type: WorkerToTabMessageTypeValue | string, payload: unknown): void {\n for (const tab of this.tabs.values()) {\n this.sendToTab(tab.port, type, payload)\n }\n }\n\n /**\n * 强制重置 Worker 状态:断开 WebSocket、清空状态,但不终止 Worker\n * 用于 forceNewWorkerOnStart 场景,让 Worker 重新接受新的连接参数\n */\n forceReset(reason?: string): void {\n console.warn('[SharedWorker] 🔄 收到强制重置指令,正在重置 Worker 状态', { reason })\n\n // 断开 WebSocket(手动关闭,防止自动重连)\n this.disconnect()\n\n // 清理缓存/状态\n this.lastMessageByType.clear()\n this.currentUrl = null\n this.currentBaseUrl = null\n this.currentUserId = null\n this.currentToken = null\n\n // 通知所有标签页已断开(但不关闭端口,让它们可以重新初始化)\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_DISCONNECTED, {})\n\n console.log('[SharedWorker] ✅ Worker 状态已重置,等待新的连接参数')\n }\n\n /**\n * 强制关闭 Worker:断开 WebSocket、清空状态、关闭所有端口\n * 用于\"退出登录/强制重置连接\",避免旧 worker 持有旧 token 影响新会话\n */\n forceShutdown(reason?: string): void {\n console.warn('[SharedWorker] ⚠️ 收到强制关闭指令,正在关闭 Worker', { reason })\n\n // 先断开 WebSocket(手动关闭,防止自动重连)\n this.disconnect()\n\n // 清理缓存/状态\n this.lastMessageByType.clear()\n this.currentUrl = null\n this.currentBaseUrl = null\n this.currentUserId = null\n this.currentToken = null\n\n // 关闭所有端口并清空 tabs\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 } catch {\n // 忽略发送消息失败(端口可能已关闭)\n }\n try {\n tab.port.close()\n } catch {\n // 忽略关闭端口失败\n }\n }\n this.tabs.clear()\n\n this.stopTabCleanup()\n\n // 显式终止 SharedWorker(否则可能在 DevTools 里仍显示存活一段时间)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const workerClose = (globalThis as any).close as undefined | (() => void)\n if (typeof workerClose === 'function') {\n console.warn('[SharedWorker] 🛑 正在终止 SharedWorker 进程')\n try {\n workerClose()\n } catch (error) {\n console.warn('[SharedWorker] 终止 SharedWorker 失败', error)\n }\n }\n }\n\n /**\n * 处理网络恢复事件\n * 重置重连计数并立即尝试重连\n */\n handleNetworkOnline(): void {\n console.log('[SharedWorker] 🌐 收到网络恢复通知')\n\n // 如果已连接,无需重连\n if (this.socket && this.socket.readyState === WebSocket.OPEN) {\n console.log('[SharedWorker] 已有活跃连接,无需重连')\n return\n }\n\n // 重置重连计数\n this.reconnectAttempts = 0\n\n // 清除可能存在的重连定时器\n this.clearReconnectTimer()\n\n // 重置熔断状态\n this.reconnectSuppressedUntil = 0\n\n // 如果有可见标签页且有 URL,立即尝试重连\n if (this.currentUrl && this.hasVisibleTab()) {\n console.log('[SharedWorker] 网络恢复,立即尝试重连')\n this.connect()\n } else {\n console.log('[SharedWorker] 网络恢复,但无可见标签页或无 URL,等待条件满足')\n }\n }\n}\n\n// 创建全局 WebSocket 管理器实例\nconst wsManager = new WebSocketManager()\n\n/**\n * SharedWorker 连接事件监听\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n;(globalThis as any).onconnect = (event: MessageEvent) => {\n const port = event.ports[0]\n\n port.onmessage = (e: MessageEvent) => {\n const message = e.data as TabToWorkerMessage\n console.log(`[SharedWorker] 📬 收到标签页消息, type: ${message.type}, tabId: ${message.tabId}`)\n // 更新该标签页最后活跃时间(心跳/任意消息都算)\n const tab = (wsManager as any).tabs?.get?.(message.tabId) as TabInfo | undefined\n if (tab) tab.lastSeen = Date.now()\n\n switch (message.type) {\n case 'TAB_INIT':\n wsManager.addTab(port, message)\n break\n\n case 'TAB_SEND':\n wsManager.send((message.payload as SendPayload).data)\n break\n\n case 'TAB_VISIBILITY':\n wsManager.updateTabVisibility(message.tabId, (message.payload as VisibilityPayload).isVisible)\n break\n\n case 'TAB_REGISTER_CALLBACK':\n console.log(`[SharedWorker] 🔔 处理注册回调请求:`, message.payload)\n wsManager.registerCallback(message.tabId, message.payload as RegisterCallbackPayload)\n break\n\n case 'TAB_UNREGISTER_CALLBACK':\n wsManager.unregisterCallback(message.tabId, message.payload as UnregisterCallbackPayload)\n break\n\n case 'TAB_DISCONNECT':\n wsManager.removeTab(message.tabId)\n break\n\n case 'TAB_PING':\n // 响应 PING(tab 侧心跳)\n port.postMessage({\n type: 'WORKER_PONG',\n timestamp: Date.now(),\n })\n break\n\n case 'TAB_FORCE_RESET':\n wsManager.forceReset((message.payload as ForceShutdownPayload)?.reason)\n break\n\n case 'TAB_FORCE_SHUTDOWN':\n wsManager.forceShutdown((message.payload as ForceShutdownPayload)?.reason)\n break\n\n case 'TAB_NETWORK_ONLINE':\n wsManager.handleNetworkOnline()\n break\n\n default:\n console.warn('[SharedWorker] 未知消息类型', message.type)\n }\n }\n\n port.start()\n}\n"],"names":["WorkerToTabMessageType","wsManager","constructor","this","tabs","Map","socket","lastMessageByType","tabCleanupTimer","idleTimer","heartbeatTimer","reconnectTimer","reconnectAttempts","manualClose","currentUrl","currentUserId","currentToken","currentBaseUrl","lastOpenAt","fastClose1000Count","reconnectSuppressedUntil","config","sharedWorkerIdleTimeout","tabStaleTimeout","hasVisibleTab","Array","from","values","some","tab","isVisible","clearReconnectTimer","clearTimeout","startTabCleanup","globalThis","setInterval","now","Date","staleTabIds","lastSeen","push","tabId","length","console","warn","forEach","id","removeTab","stopTabCleanup","clearInterval","addTab","port","message","payload","initPayload","userId","conflictPayload","newUserId","sendToTab","tabInfo","registeredTypes","Set","callbackMap","set","log","size","nextBaseUrl","url","nextUserId","nextToken","token","nextUrl","encodeURIComponent","hasExistingIdentity","identityChanged","broadcastToAllTabs","clear","disconnect","readyState","WebSocket","OPEN","checkAllTabsVisibility","delete","startIdleTimer","updateTabVisibility","get","every","resetIdleTimer","connect","clearIdleTimer","setTimeout","suppressedUntil","CONNECTING","onopen","startHeartbeat","onmessage","event","handleIncoming","data","onclose","liveMs","code","reason","wasClean","stopHeartbeat","errorPayload","error","autoReconnect","scheduleReconnect","onerror","maxAttempts","maxReconnectAttempts","reconnectDelay","reconnectDelayMax","delay","Math","min","close","heartbeatInterval","heartbeatData","type","timestamp","send","JSON","stringify","parse","serverMessagePayload","sentCount","has","registerCallback","add","callbackId","cached","unregisterCallback","t","entries","postMessage","forceReset","forceShutdown","workerClose","handleNetworkOnline","onconnect","ports","e","start"],"mappings":"aASA,MAAMA,EAEY,iBAFZA,EAGc,mBAHdA,EAIiB,sBAJjBA,EAKU,eALVA,EAMkB,uBAo1BxB,MAAMC,EAAY,IAnvBlB,MAAA,WAAAC,GAEUC,KAAAC,KAA6B,IAAIC,IAGjCF,KAAAG,OAA2B,KAG3BH,KAAAI,kBAAuD,IAAIF,IAG3DF,KAAAK,gBAAyD,KAGzDL,KAAAM,UAAkD,KAGlDN,KAAAO,eAAwD,KAGxDP,KAAAQ,eAAuD,KAGvDR,KAAAS,kBAAoB,EAGpBT,KAAAU,aAAc,EAGdV,KAAAW,WAA4B,KAG5BX,KAAAY,cAA+B,KAG/BZ,KAAAa,aAA8B,KAG9Bb,KAAAc,eAAgC,KAGhCd,KAAAe,WAAa,EAGbf,KAAAgB,mBAAqB,EAGrBhB,KAAAiB,yBAA2B,EAG3BjB,KAAAkB,OAAuC,KAGvClB,KAAAmB,wBAA0B,IAG1BnB,KAAAoB,gBAAkB,IAwrB5B,CAnrBU,aAAAC,GACN,OAAOC,MAAMC,KAAKvB,KAAKC,KAAKuB,UAAUC,KAAMC,GAAQA,EAAIC,UAC1D,CAKQ,mBAAAC,GACsB,OAAxB5B,KAAKQ,iBACPqB,aAAa7B,KAAKQ,gBAClBR,KAAKQ,eAAiB,KAE1B,CAKQ,eAAAsB,GACuB,OAAzB9B,KAAKK,kBAGTL,KAAKK,gBAAmB0B,WAAmBC,YAAY,KACrD,MAAMC,EAAMC,KAAKD,MACXE,EAAwB,GAC9B,IAAK,MAAMT,KAAO1B,KAAKC,KAAKuB,SACtBS,EAAMP,EAAIU,SAAWpC,KAAKoB,iBAC5Be,EAAYE,KAAKX,EAAIY,OAIrBH,EAAYI,OAAS,IACvBC,QAAQC,KAAK,+BAAgCN,GAC7CA,EAAYO,QAASC,GAAO3C,KAAK4C,UAAUD,MAE5C,MACL,CAEQ,cAAAE,GACuB,OAAzB7C,KAAKK,kBACPyC,cAAc9C,KAAKK,iBACnBL,KAAKK,gBAAkB,KAE3B,CAKA,MAAA0C,CAAOC,EAAmBC,GACxB,MAAMX,MAAEA,EAAKY,QAAEA,GAAYD,EACrBE,EAAcD,EAGpB,GAAIlD,KAAKY,eAAiBZ,KAAKY,gBAAkBuC,EAAYC,OAAQ,CACnE,MAAMC,EAAuC,CAC3CzC,cAAeZ,KAAKY,cACpB0C,UAAWH,EAAYC,OACvBH,QAAS,qBAAqBjD,KAAKY,4BAA4BuC,EAAYC,sBAG7EpD,KAAKuD,UAAUP,EAAMnD,EAA6CwD,GAClEb,QAAQC,KAAK,iBAAkBY,EAAgBJ,QACjD,CAGA,MAAMO,EAAmB,CACvBR,OACAV,QACAX,UAAWwB,EAAYxB,UACvBS,SAAUF,KAAKD,MACfwB,gBAAiB,IAAIC,IACrBC,YAAa,IAAIzD,KAGnBF,KAAKC,KAAK2D,IAAItB,EAAOkB,GACrBhB,QAAQqB,IAAI,0BAA0BvB,eAAmBtC,KAAKC,KAAK6D,QACnE9D,KAAK8B,kBAGL,MAAMiC,EAAcZ,EAAYa,IAC1BC,EAAad,EAAYC,OACzBc,EAAYf,EAAYgB,MACxBC,EAAU,GAAGL,KAAeE,WAAoBI,mBAAmBH,KAEnEI,EACoB,OAAxBtE,KAAKc,gBACkB,OAAvBd,KAAKY,eACiB,OAAtBZ,KAAKa,cACe,OAApBb,KAAKW,WACD4D,EACqB,OAAxBvE,KAAKc,gBAA2Bd,KAAKc,iBAAmBiD,GACjC,OAAvB/D,KAAKY,eAA0BZ,KAAKY,gBAAkBqD,GAChC,OAAtBjE,KAAKa,cAAyBb,KAAKa,eAAiBqD,GAChC,OAApBlE,KAAKW,YAAuBX,KAAKW,aAAeyD,EAEnD,IAAKE,GAAuBC,EAAiB,CAC3C,GAAID,EAAqB,CACvB,MAAMjB,EAAuC,CAC3CzC,cAAeZ,KAAKY,eAAiB,GACrC0C,UAAWW,EACXhB,QAEE,wBAAWjD,KAAKY,eAAiB,qBAAqBqD,iBACxCjE,KAAKc,gBAAkB,wBAAwBiD,oBAC7C/D,KAAKa,cAAeb,KAAKa,eAAiBqD,qBAI9DlE,KAAKwE,mBAAmB3E,EAA6CwD,GACrEb,QAAQC,KAAK,iBAAkBY,EAAgBJ,QACjD,CAGAjD,KAAKc,eAAiBiD,EACtB/D,KAAKY,cAAgBqD,EACrBjE,KAAKa,aAAeqD,EACpBlE,KAAKW,WAAayD,EAClBpE,KAAKkB,OAASiC,EAAYjC,OAC1BlB,KAAKmB,wBAA0BgC,EAAYhC,yBAA2B,IAGtEnB,KAAKI,kBAAkBqE,QAGnBzE,KAAKG,SACPqC,QAAQqB,IAAI,uCACZ7D,KAAK0E,aAET,MAEE1E,KAAKkB,OAASiC,EAAYjC,OAC1BlB,KAAKmB,wBAA0BgC,EAAYhC,yBAA2BnB,KAAKmB,wBAIzEnB,KAAKG,QAAUH,KAAKG,OAAOwE,aAAeC,UAAUC,MACtD7E,KAAKuD,UAAUP,EAAMnD,EAAyC,CAAA,GAIhEG,KAAK8E,wBACP,CAKA,SAAAlC,CAAUN,GACRtC,KAAKC,KAAK8E,OAAOzC,GACjBE,QAAQqB,IAAI,0BAA0BvB,eAAmBtC,KAAKC,KAAK6D,QAE5C,IAAnB9D,KAAKC,KAAK6D,MAEZtB,QAAQqB,IAAI,8BAA8B7D,KAAKmB,mCAC/CnB,KAAK4B,sBACL5B,KAAK6C,iBACL7C,KAAKgF,kBAGLhF,KAAK8E,wBAET,CAKA,mBAAAG,CAAoB3C,EAAeX,GACjC,MAAMD,EAAM1B,KAAKC,KAAKiF,IAAI5C,GACrBZ,GAKLA,EAAIC,UAAYA,EAChBD,EAAIU,SAAWF,KAAKD,MACpBO,QAAQqB,IAAI,sBAAsBvB,YAAgBX,KAElD3B,KAAK8E,0BARHtC,QAAQC,KAAK,0BAA0BH,IAS3C,CAKQ,sBAAAwC,GACN,GAAuB,IAAnB9E,KAAKC,KAAK6D,KAAY,OAERxC,MAAMC,KAAKvB,KAAKC,KAAKuB,UAAU2D,MAAOzD,IAASA,EAAIC,YAInEa,QAAQqB,IAAI,+BAA+B7D,KAAKmB,mCAEhDnB,KAAK4B,sBACL5B,KAAKgF,mBAGLxC,QAAQqB,IAAI,kCACZ7D,KAAKoF,iBAGApF,KAAKG,QAAUH,KAAKG,OAAOwE,aAAeC,UAAUC,MACnD7E,KAAKW,aACP6B,QAAQqB,IAAI,wCACZ7D,KAAKqF,WAIb,CAKQ,cAAAL,GACNhF,KAAKsF,iBAGLtF,KAAKM,UAAayB,WAAmBwD,WAAW,KAC9C/C,QAAQqB,IAAI,4BACZ7D,KAAK0E,cACJ1E,KAAKmB,wBACV,CAKQ,cAAAiE,GACNpF,KAAKsF,gBACP,CAKQ,cAAAA,GACiB,OAAnBtF,KAAKM,YACPuB,aAAa7B,KAAKM,WAClBN,KAAKM,UAAY,KAErB,CAKQ,OAAA+E,GACN,GAAKrF,KAAKW,WAMV,GAAIuB,KAAKD,MAAQjC,KAAKiB,yBACpBuB,QAAQC,KAAK,8BAA+B,CAC1C+C,gBAAiBxF,KAAKiB,gCAM1B,GAAKjB,KAAKqB,iBAMV,IAAIrB,KAAKG,QAAWH,KAAKG,OAAOwE,aAAeC,UAAUC,MAAQ7E,KAAKG,OAAOwE,aAAeC,UAAUa,WAAtG,CAIAzF,KAAKU,aAAc,EACnBV,KAAK4B,sBAEL,IACE5B,KAAKG,OAAS,IAAIyE,UAAU5E,KAAKW,YAEjCX,KAAKG,OAAOuF,OAAS,KACnBlD,QAAQqB,IAAI,mCACZ7D,KAAKS,kBAAoB,EACzBT,KAAKe,WAAamB,KAAKD,MACvBjC,KAAKgB,mBAAqB,EAC1BhB,KAAK2F,iBAGLnD,QAAQqB,IAAI,qBAAqB7D,KAAKC,KAAK6D,kBAC3C9D,KAAKwE,mBAAmB3E,EAAyC,CAAA,IAGnEG,KAAKG,OAAOyF,UAAaC,IACvB7F,KAAK8F,eAAeD,EAAME,OAG5B/F,KAAKG,OAAO6F,QAAWH,IACrB,MAAM5D,EAAMC,KAAKD,MACXgE,EAASjG,KAAKe,WAAakB,EAAMjC,KAAKe,YAAc,EAa1D,GAZAyB,QAAQqB,IAAI,gCAAiC,CAC3CqC,KAAML,EAAMK,KACZC,OAAQN,EAAMM,OACdC,SAAWP,EAAcO,SACzBH,WAEFjG,KAAKqG,gBAGLrG,KAAKwE,mBAAmB3E,EAA4C,CAAA,GAGjD,MAAfgG,EAAMK,MAAiBD,GAAU,GAAKA,EAAS,KAQjD,GAPAjG,KAAKgB,oBAAsB,EAC3BwB,QAAQC,KAAK,+BAAgC,CAC3CzB,mBAAoBhB,KAAKgB,mBACzBiF,WAIEjG,KAAKgB,oBAAsB,EAAG,CAChChB,KAAKiB,yBAA2BgB,EAAM,IACtC,MAAMqE,EAA6B,CACjCrD,QACE,+EACFsD,MAAO,CACLL,KAAML,EAAMK,KACZC,OAAQN,EAAMM,OACdF,WAIJ,YADAjG,KAAKwE,mBAAmB3E,EAAqCyG,EAE/D,OAGAtG,KAAKgB,mBAAqB,GAIvBhB,KAAKU,aAAeV,KAAKkB,QAAQsF,eAAiBxG,KAAKC,KAAK6D,KAAO,GAAK9D,KAAKqB,iBAChFrB,KAAKyG,qBAITzG,KAAKG,OAAOuG,QAAWb,IACrBrD,QAAQ+D,MAAM,gCAAiCV,GAC/C7F,KAAKqG,gBAEL,MAAMC,EAA6B,CACjCrD,QAAS,iBACTsD,MAAOV,GAIT7F,KAAKwE,mBAAmB3E,EAAqCyG,GAEjE,CAAE,MAAOC,GACP/D,QAAQ+D,MAAM,mCAAoCA,GAElD,MAAMD,EAA6B,CACjCrD,QAAS,oBACTsD,SAGFvG,KAAKwE,mBAAmB3E,EAAqCyG,GAEzDtG,KAAKkB,QAAQsF,gBAAkBxG,KAAKU,aACtCV,KAAKyG,mBAET,CAjGA,OAPEjE,QAAQqB,IAAI,qCAdZrB,QAAQ+D,MAAM,kCAuHlB,CAKQ,iBAAAE,GAEN,GAAuB,IAAnBzG,KAAKC,KAAK6D,OAAe9D,KAAKqB,gBAChC,OAGF,MAAMsF,EAAc3G,KAAKkB,QAAQ0F,sBAAwB,GACnDC,EAAiB7G,KAAKkB,QAAQ2F,gBAAkB,IAChDC,EAAoB9G,KAAKkB,QAAQ4F,mBAAqB,IAE5D,GAAI9G,KAAKS,mBAAqBkG,IAAgB3G,KAAKW,YAAcX,KAAKU,YAIpE,YAHIV,KAAKS,mBAAqBkG,GAC5BnE,QAAQC,KAAK,6BAKjBzC,KAAKS,mBAAqB,EAC1B,MAAMsG,EAAQC,KAAKC,IAAIJ,EAAiB7G,KAAKS,kBAAmBqG,GAEhEtE,QAAQqB,IAAI,qBAAqBkD,YAAgB/G,KAAKS,yBAEtDT,KAAK4B,sBACL5B,KAAKQ,eAAkBuB,WAAmBwD,WAAW,KACnDvF,KAAKqF,WACJ0B,EACL,CAKQ,UAAArC,GACN1E,KAAKU,aAAc,EACnBV,KAAKqG,gBACLrG,KAAKsF,iBAELtF,KAAK4B,sBAED5B,KAAKG,SACPH,KAAKG,OAAO+G,QACZlH,KAAKG,OAAS,MAGhBH,KAAKS,kBAAoB,CAC3B,CAKQ,cAAAkF,GACN3F,KAAKqG,gBAEL,MAAMc,EAAoBnH,KAAKkB,QAAQiG,mBAAqB,KAG5DnH,KAAKO,eAAkBwB,WAAmBC,YAAY,KACpD,IAAKhC,KAAKG,QAAUH,KAAKG,OAAOwE,aAAeC,UAAUC,KACvD,OAIF,MAAMuC,EAAgB,CAAEC,KAAM,OAAQC,UAAWpF,KAAKD,OACtDjC,KAAKuH,KAAKH,IACTD,EACL,CAKQ,aAAAd,GACsB,OAAxBrG,KAAKO,iBACPuC,cAAc9C,KAAKO,gBACnBP,KAAKO,eAAiB,KAE1B,CAKA,IAAAgH,CAAKxB,GACH,IAAK/F,KAAKG,QAAUH,KAAKG,OAAOwE,aAAeC,UAAUC,KAEvD,YADArC,QAAQC,KAAK,uCAIf,MAAMQ,EAA0B,iBAAT8C,EAAoBA,EAAOyB,KAAKC,UAAU1B,GACjE/F,KAAKG,OAAOoH,KAAKtE,EACnB,CAKQ,cAAA6C,CAAeC,GACrB,IAAKA,EAAM,OAEX,IAAI9C,EACJ,IACEA,EAAUuE,KAAKE,MAAM3B,EACvB,CAAE,MAEA,YADAvD,QAAQC,KAAK,wBAAyBsD,EAExC,CAEA,IAAK9C,GAASoE,KACZ,OAGF7E,QAAQqB,IAAI,oCAAoCZ,EAAQoE,OAAQpE,GAGhE,MAAM0E,EAA6C,CACjD5B,OACA9C,WAIFjD,KAAKI,kBAAkBwD,IAAIX,EAAQoE,KAAMM,GAEzC,IAAIC,EAAY,EAChB,IAAK,MAAMlG,KAAO1B,KAAKC,KAAKuB,SAC1BgB,QAAQqB,IAAI,wBAAwBnC,EAAIY,gBAAiBhB,MAAMC,KAAKG,EAAI+B,kBACpE/B,EAAI+B,gBAAgBoE,IAAI5E,EAAQoE,QAClC7E,QAAQqB,IAAI,6BAA6BnC,EAAIY,gBAAgBW,EAAQoE,QACrErH,KAAKuD,UAAU7B,EAAIsB,KAAMnD,EAAuC8H,GAChEC,KAIJpF,QAAQqB,IAAI,gCAAgCZ,EAAQoE,aAAaO,SACnE,CAKA,gBAAAE,CAAiBxF,EAAeY,GAC9B,MAAMxB,EAAM1B,KAAKC,KAAKiF,IAAI5C,GAC1B,IAAKZ,EAEH,YADAc,QAAQC,KAAK,6BAA6BH,KAI5CZ,EAAIU,SAAWF,KAAKD,MACpBP,EAAI+B,gBAAgBsE,IAAI7E,EAAQmE,MAChC3F,EAAIiC,YAAYC,IAAIV,EAAQ8E,WAAY9E,EAAQmE,MAChD7E,QAAQqB,IAAI,wBAAwBvB,WAAeY,EAAQmE,SAASnE,EAAQ8E,eAC5ExF,QAAQqB,IAAI,sBAAsBvB,eAAoBhB,MAAMC,KAAKG,EAAI+B,kBAGrE,MAAMwE,EAASjI,KAAKI,kBAAkB8E,IAAIhC,EAAQmE,MAC9CY,IACFzF,QAAQqB,IAAI,gCAAgCvB,YAAgBY,EAAQmE,QACpErH,KAAKuD,UAAU7B,EAAIsB,KAAMnD,EAAuCoI,GAEpE,CAKA,kBAAAC,CAAmB5F,EAAeY,GAChC,MAAMxB,EAAM1B,KAAKC,KAAKiF,IAAI5C,GAC1B,GAAKZ,EAML,GADAA,EAAIU,SAAWF,KAAKD,MAChBiB,EAAQ8E,WAAY,CAEtB,MAAMX,EAAO3F,EAAIiC,YAAYuB,IAAIhC,EAAQ8E,YACzC,GAAIX,EAAM,CACR3F,EAAIiC,YAAYoB,OAAO7B,EAAQ8E,YAGL1G,MAAMC,KAAKG,EAAIiC,YAAYnC,UAAUC,KAAM0G,GAAMA,IAAMd,IAE/E3F,EAAI+B,gBAAgBsB,OAAOsC,GAG7B7E,QAAQqB,IAAI,sBAAsBvB,aAAiB+E,IACrD,CACF,KAAO,CAEL3F,EAAI+B,gBAAgBsB,OAAO7B,EAAQmE,MAGnC,IAAK,MAAOW,EAAYX,KAAS3F,EAAIiC,YAAYyE,UAC3Cf,IAASnE,EAAQmE,MACnB3F,EAAIiC,YAAYoB,OAAOiD,GAI3BxF,QAAQqB,IAAI,sBAAsBvB,YAAgBY,EAAQmE,YAC5D,MA/BE7E,QAAQC,KAAK,0BAA0BH,IAgC3C,CAKQ,SAAAiB,CAAUP,EAAmBqE,EAA4CnE,GAC/E,MAAMD,EAA8B,CAClCoE,OACAnE,UACAoE,UAAWpF,KAAKD,OAGlB,IACEe,EAAKqF,YAAYpF,EACnB,CAAE,MAAOsD,GACP/D,QAAQ+D,MAAM,4BAA6BA,EAC7C,CACF,CAKQ,kBAAA/B,CAAmB6C,EAA4CnE,GACrE,IAAK,MAAMxB,KAAO1B,KAAKC,KAAKuB,SAC1BxB,KAAKuD,UAAU7B,EAAIsB,KAAMqE,EAAMnE,EAEnC,CAMA,UAAAoF,CAAWnC,GACT3D,QAAQC,KAAK,4CAA6C,CAAE0D,WAG5DnG,KAAK0E,aAGL1E,KAAKI,kBAAkBqE,QACvBzE,KAAKW,WAAa,KAClBX,KAAKc,eAAiB,KACtBd,KAAKY,cAAgB,KACrBZ,KAAKa,aAAe,KAGpBb,KAAKwE,mBAAmB3E,EAA4C,CAAA,GAEpE2C,QAAQqB,IAAI,yCACd,CAMA,aAAA0E,CAAcpC,GACZ3D,QAAQC,KAAK,yCAA0C,CAAE0D,WAGzDnG,KAAK0E,aAGL1E,KAAKI,kBAAkBqE,QACvBzE,KAAKW,WAAa,KAClBX,KAAKc,eAAiB,KACtBd,KAAKY,cAAgB,KACrBZ,KAAKa,aAAe,KAGpB,IAAK,MAAMa,KAAO1B,KAAKC,KAAKuB,SAAU,CACpC,IACEE,EAAIsB,KAAKqF,YAAY,CACnBhB,KAAMxH,EACNqD,QAAS,CAAA,EACToE,UAAWpF,KAAKD,OAEpB,CAAE,MAEF,CACA,IACEP,EAAIsB,KAAKkE,OACX,CAAE,MAEF,CACF,CACAlH,KAAKC,KAAKwE,QAEVzE,KAAK6C,iBAIL,MAAM2F,EAAezG,WAAmBmF,MACxC,GAA2B,mBAAhBsB,EAA4B,CACrChG,QAAQC,KAAK,0CACb,IACE+F,GACF,CAAE,MAAOjC,GACP/D,QAAQC,KAAK,oCAAqC8D,EACpD,CACF,CACF,CAMA,mBAAAkC,GACEjG,QAAQqB,IAAI,8BAGR7D,KAAKG,QAAUH,KAAKG,OAAOwE,aAAeC,UAAUC,KACtDrC,QAAQqB,IAAI,+BAKd7D,KAAKS,kBAAoB,EAGzBT,KAAK4B,sBAGL5B,KAAKiB,yBAA2B,EAG5BjB,KAAKW,YAAcX,KAAKqB,iBAC1BmB,QAAQqB,IAAI,8BACZ7D,KAAKqF,WAEL7C,QAAQqB,IAAI,4CAEhB,GAUA9B,WAAmB2G,UAAa7C,IAChC,MAAM7C,EAAO6C,EAAM8C,MAAM,GAEzB3F,EAAK4C,UAAagD,IAChB,MAAM3F,EAAU2F,EAAE7C,KAClBvD,QAAQqB,IAAI,oCAAoCZ,EAAQoE,gBAAgBpE,EAAQX,SAEhF,MAAMZ,EAAO5B,EAAkBG,MAAMiF,MAAMjC,EAAQX,OAGnD,OAFIZ,IAAKA,EAAIU,SAAWF,KAAKD,OAErBgB,EAAQoE,MACd,IAAK,WACHvH,EAAUiD,OAAOC,EAAMC,GACvB,MAEF,IAAK,WACHnD,EAAUyH,KAAMtE,EAAQC,QAAwB6C,MAChD,MAEF,IAAK,iBACHjG,EAAUmF,oBAAoBhC,EAAQX,MAAQW,EAAQC,QAA8BvB,WACpF,MAEF,IAAK,wBACHa,QAAQqB,IAAI,8BAA+BZ,EAAQC,SACnDpD,EAAUgI,iBAAiB7E,EAAQX,MAAOW,EAAQC,SAClD,MAEF,IAAK,0BACHpD,EAAUoI,mBAAmBjF,EAAQX,MAAOW,EAAQC,SACpD,MAEF,IAAK,iBACHpD,EAAU8C,UAAUK,EAAQX,OAC5B,MAEF,IAAK,WAEHU,EAAKqF,YAAY,CACfhB,KAAM,cACNC,UAAWpF,KAAKD,QAElB,MAEF,IAAK,kBACHnC,EAAUwI,WAAYrF,EAAQC,SAAkCiD,QAChE,MAEF,IAAK,qBACHrG,EAAUyI,cAAetF,EAAQC,SAAkCiD,QACnE,MAEF,IAAK,qBACHrG,EAAU2I,sBACV,MAEF,QACEjG,QAAQC,KAAK,wBAAyBQ,EAAQoE,QAIpDrE,EAAK6F"}
@@ -28,6 +28,8 @@ interface TabInfo {
28
28
  port: MessagePort;
29
29
  tabId: string;
30
30
  isVisible: boolean;
31
+ /** 最近一次收到该标签页心跳/消息的时间戳 */
32
+ lastSeen: number;
31
33
  registeredTypes: Set<string>;
32
34
  callbackMap: Map<string, string>;
33
35
  }
@@ -46,6 +48,9 @@ interface InitPayload {
46
48
  };
47
49
  sharedWorkerIdleTimeout?: number;
48
50
  }
51
+ interface ForceShutdownPayload {
52
+ reason?: string;
53
+ }
49
54
  interface SendPayload {
50
55
  data: string | object;
51
56
  }
@@ -87,6 +92,10 @@ declare class WebSocketManager {
87
92
  private tabs;
88
93
  /** WebSocket 连接实例 */
89
94
  private socket;
95
+ /** 最近一次服务器消息缓存(按 type) */
96
+ private lastMessageByType;
97
+ /** 标签页清理定时器(回收已关闭/崩溃的标签页) */
98
+ private tabCleanupTimer;
90
99
  /** 空闲定时器 */
91
100
  private idleTimer;
92
101
  /** 心跳定时器 */
@@ -101,10 +110,35 @@ declare class WebSocketManager {
101
110
  private currentUrl;
102
111
  /** 当前用户ID */
103
112
  private currentUserId;
113
+ /** 当前 token(用于判断登录态变化) */
114
+ private currentToken;
115
+ /** 当前 baseUrl(用于判断环境/域名变化) */
116
+ private currentBaseUrl;
117
+ /** 上次连接打开时间 */
118
+ private lastOpenAt;
119
+ /** 连续快速被服务端正常关闭(1000)次数 */
120
+ private fastClose1000Count;
121
+ /** 自动重连熔断到期时间戳(ms) */
122
+ private reconnectSuppressedUntil;
104
123
  /** 配置 */
105
124
  private config;
106
125
  /** SharedWorker 空闲超时时间(毫秒) */
107
126
  private sharedWorkerIdleTimeout;
127
+ /** tab 超时时间(毫秒):超过该时间未心跳则认为已关闭 */
128
+ private tabStaleTimeout;
129
+ /**
130
+ * 是否存在可见标签页
131
+ */
132
+ private hasVisibleTab;
133
+ /**
134
+ * 清理重连定时器
135
+ */
136
+ private clearReconnectTimer;
137
+ /**
138
+ * 启动 tab 清理(兜底:页面强制关闭/崩溃未发送 TAB_DISCONNECT 时也能回收)
139
+ */
140
+ private startTabCleanup;
141
+ private stopTabCleanup;
108
142
  /**
109
143
  * 添加标签页
110
144
  */
@@ -177,6 +211,21 @@ declare class WebSocketManager {
177
211
  * 广播消息到所有标签页
178
212
  */
179
213
  private broadcastToAllTabs;
214
+ /**
215
+ * 强制重置 Worker 状态:断开 WebSocket、清空状态,但不终止 Worker
216
+ * 用于 forceNewWorkerOnStart 场景,让 Worker 重新接受新的连接参数
217
+ */
218
+ forceReset(reason?: string): void;
219
+ /**
220
+ * 强制关闭 Worker:断开 WebSocket、清空状态、关闭所有端口
221
+ * 用于"退出登录/强制重置连接",避免旧 worker 持有旧 token 影响新会话
222
+ */
223
+ forceShutdown(reason?: string): void;
224
+ /**
225
+ * 处理网络恢复事件
226
+ * 重置重连计数并立即尝试重连
227
+ */
228
+ handleNetworkOnline(): void;
180
229
  }
181
230
  declare const wsManager: WebSocketManager;
182
231
  //# sourceMappingURL=worker-script.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"worker-script.d.ts","sourceRoot":"","sources":["../src/worker-script.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"worker-script.d.ts","sourceRoot":"","sources":["../src/worker-script.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e="WORKER_MESSAGE",t="WORKER_CONNECTED",s="WORKER_DISCONNECTED",r="WORKER_ERROR",o="WORKER_AUTH_CONFLICT";const a=new class{constructor(){this.tabs=new Map,this.socket=null,this.idleTimer=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.reconnectAttempts=0,this.manualClose=!1,this.currentUrl=null,this.currentUserId=null,this.config=null,this.sharedWorkerIdleTimeout=3e4}addTab(e,s){const{tabId:r,payload:a}=s,i=a;if(this.currentUserId&&this.currentUserId!==i.userId){const t={currentUserId:this.currentUserId,newUserId:i.userId,message:`检测到不同用户身份:当前连接用户为 ${this.currentUserId},新标签页尝试使用用户 ${i.userId} 连接。将复用现有连接。`};this.sendToTab(e,o,t),console.warn("[SharedWorker]",t.message)}const c={port:e,tabId:r,isVisible:i.isVisible,registeredTypes:new Set,callbackMap:new Map};this.tabs.set(r,c),console.log(`[SharedWorker] 标签页已添加: ${r}, 当前标签页数量: ${this.tabs.size}`),this.socket&&this.socket.readyState===WebSocket.OPEN?this.sendToTab(e,t,{}):(this.currentUrl=`${i.url}/${i.userId}?token=${encodeURIComponent(i.token)}`,this.currentUserId=i.userId,this.config=i.config,this.sharedWorkerIdleTimeout=i.sharedWorkerIdleTimeout??3e4,this.connect()),this.resetIdleTimer()}removeTab(e){this.tabs.delete(e),console.log(`[SharedWorker] 标签页已移除: ${e}, 剩余标签页数量: ${this.tabs.size}`),0===this.tabs.size?(console.log(`[SharedWorker] 所有标签页已关闭,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`),this.startIdleTimer()):this.checkAllTabsVisibility()}updateTabVisibility(e,t){const s=this.tabs.get(e);s?(s.isVisible=t,console.log(`[SharedWorker] 标签页 ${e} 可见性更新: ${t}`),this.checkAllTabsVisibility()):console.warn(`[SharedWorker] 标签页不存在: ${e}`)}checkAllTabsVisibility(){if(0===this.tabs.size)return;Array.from(this.tabs.values()).every(e=>!e.isVisible)?(console.log(`[SharedWorker] 所有标签页都不可见,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`),this.startIdleTimer()):(console.log("[SharedWorker] 至少有一个标签页可见,保持连接"),this.resetIdleTimer())}startIdleTimer(){this.clearIdleTimer(),this.idleTimer=globalThis.setTimeout(()=>{console.log("[SharedWorker] 空闲超时,断开连接"),this.disconnect()},this.sharedWorkerIdleTimeout)}resetIdleTimer(){this.clearIdleTimer()}clearIdleTimer(){null!==this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}connect(){if(this.currentUrl){this.manualClose=!1;try{this.socket=new WebSocket(this.currentUrl),this.socket.onopen=()=>{console.log("[SharedWorker] ✅ WebSocket 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),console.log(`[SharedWorker] 通知 ${this.tabs.size} 个标签页: 已连接`),this.broadcastToAllTabs(t,{})},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{console.log("[SharedWorker] WebSocket 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.broadcastToAllTabs(s,{}),!this.manualClose&&this.config?.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{console.error("[SharedWorker] WebSocket 连接错误",e),this.stopHeartbeat();const t={message:"WebSocket 连接错误",error:e};this.broadcastToAllTabs(r,t)}}catch(e){console.error("[SharedWorker] 创建 WebSocket 连接失败",e);const t={message:"创建 WebSocket 连接失败",error:e};this.broadcastToAllTabs(r,t),this.config?.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else console.error("[SharedWorker] 缺少 WebSocket URL")}scheduleReconnect(){const e=this.config?.maxReconnectAttempts??10,t=this.config?.reconnectDelay??3e3,s=this.config?.reconnectDelayMax??1e4;if(this.reconnectAttempts>=e||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=e&&console.warn("[SharedWorker] 已达到最大重连次数"));this.reconnectAttempts+=1;const r=Math.min(t*this.reconnectAttempts,s);console.log(`[SharedWorker] 将在 ${r}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=globalThis.setTimeout(()=>{this.connect()},r)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.clearIdleTimer(),null!==this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.reconnectAttempts=0}startHeartbeat(){this.stopHeartbeat();const e=this.config?.heartbeatInterval??25e3;this.heartbeatTimer=globalThis.setInterval(()=>{if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return;const e={type:"PING",timestamp:Date.now()};this.send(e)},e)}stopHeartbeat(){null!==this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void console.warn("[SharedWorker] WebSocket 未连接,无法发送消息");const t="string"==typeof e?e:JSON.stringify(e);this.socket.send(t)}handleIncoming(t){if(!t)return;let s;try{s=JSON.parse(t)}catch{return void console.warn("[SharedWorker] 无法解析消息",t)}if(!s?.type)return;console.log(`[SharedWorker] 📨 收到服务器消息, type: ${s.type}`,s);const r={data:t,message:s};let o=0;for(const t of this.tabs.values())console.log(`[SharedWorker] 检查标签页 ${t.tabId}, 注册的类型:`,Array.from(t.registeredTypes)),t.registeredTypes.has(s.type)&&(console.log(`[SharedWorker] ✅ 发送消息到标签页 ${t.tabId}, type: ${s.type}`),this.sendToTab(t.port,e,r),o++);console.log(`[SharedWorker] 消息分发完成, type: ${s.type}, 发送给 ${o} 个标签页`)}registerCallback(e,t){const s=this.tabs.get(e);s?(s.registeredTypes.add(t.type),s.callbackMap.set(t.callbackId,t.type),console.log(`[SharedWorker] ✅ 标签页 ${e} 注册回调: ${t.type} (${t.callbackId})`),console.log(`[SharedWorker] 标签页 ${e} 当前注册的所有类型:`,Array.from(s.registeredTypes))):console.warn(`[SharedWorker] ⚠️ 标签页不存在: ${e}`)}unregisterCallback(e,t){const s=this.tabs.get(e);if(s)if(t.callbackId){const r=s.callbackMap.get(t.callbackId);if(r){s.callbackMap.delete(t.callbackId);Array.from(s.callbackMap.values()).some(e=>e===r)||s.registeredTypes.delete(r),console.log(`[SharedWorker] 标签页 ${e} 取消注册回调: ${r}`)}}else{s.registeredTypes.delete(t.type);for(const[e,r]of s.callbackMap.entries())r===t.type&&s.callbackMap.delete(e);console.log(`[SharedWorker] 标签页 ${e} 取消注册所有 ${t.type} 类型回调`)}else console.warn(`[SharedWorker] 标签页不存在: ${e}`)}sendToTab(e,t,s){const r={type:t,payload:s,timestamp:Date.now()};try{e.postMessage(r)}catch(e){console.error("[SharedWorker] 发送消息到标签页失败",e)}}broadcastToAllTabs(e,t){for(const s of this.tabs.values())this.sendToTab(s.port,e,t)}};globalThis.onconnect=e=>{const t=e.ports[0];t.onmessage=e=>{const s=e.data;switch(console.log(`[SharedWorker] 📬 收到标签页消息, type: ${s.type}, tabId: ${s.tabId}`),s.type){case"TAB_INIT":a.addTab(t,s);break;case"TAB_SEND":a.send(s.payload.data);break;case"TAB_VISIBILITY":a.updateTabVisibility(s.tabId,s.payload.isVisible);break;case"TAB_REGISTER_CALLBACK":console.log("[SharedWorker] 🔔 处理注册回调请求:",s.payload),a.registerCallback(s.tabId,s.payload);break;case"TAB_UNREGISTER_CALLBACK":a.unregisterCallback(s.tabId,s.payload);break;case"TAB_DISCONNECT":a.removeTab(s.tabId);break;case"TAB_PING":t.postMessage({type:"WORKER_PONG",timestamp:Date.now()});break;default:console.warn("[SharedWorker] 未知消息类型",s.type)}},t.start()};
1
+ const e="WORKER_MESSAGE",t="WORKER_CONNECTED",s="WORKER_DISCONNECTED",r="WORKER_ERROR",o="WORKER_AUTH_CONFLICT";const a=new class{constructor(){this.tabs=new Map,this.socket=null,this.lastMessageByType=new Map,this.tabCleanupTimer=null,this.idleTimer=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.reconnectAttempts=0,this.manualClose=!1,this.currentUrl=null,this.currentUserId=null,this.currentToken=null,this.currentBaseUrl=null,this.lastOpenAt=0,this.fastClose1000Count=0,this.reconnectSuppressedUntil=0,this.config=null,this.sharedWorkerIdleTimeout=3e4,this.tabStaleTimeout=45e3}hasVisibleTab(){return Array.from(this.tabs.values()).some(e=>e.isVisible)}clearReconnectTimer(){null!==this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null)}startTabCleanup(){null===this.tabCleanupTimer&&(this.tabCleanupTimer=globalThis.setInterval(()=>{const e=Date.now(),t=[];for(const s of this.tabs.values())e-s.lastSeen>this.tabStaleTimeout&&t.push(s.tabId);t.length>0&&(console.warn("[SharedWorker] 检测到过期标签页,将清理:",t),t.forEach(e=>this.removeTab(e)))},15e3))}stopTabCleanup(){null!==this.tabCleanupTimer&&(clearInterval(this.tabCleanupTimer),this.tabCleanupTimer=null)}addTab(e,s){const{tabId:r,payload:a}=s,n=a;if(this.currentUserId&&this.currentUserId!==n.userId){const t={currentUserId:this.currentUserId,newUserId:n.userId,message:`检测到不同用户身份:当前连接用户为 ${this.currentUserId},新标签页尝试使用用户 ${n.userId} 连接。将复用现有连接。`};this.sendToTab(e,o,t),console.warn("[SharedWorker]",t.message)}const l={port:e,tabId:r,isVisible:n.isVisible,lastSeen:Date.now(),registeredTypes:new Set,callbackMap:new Map};this.tabs.set(r,l),console.log(`[SharedWorker] 标签页已添加: ${r}, 当前标签页数量: ${this.tabs.size}`),this.startTabCleanup();const i=n.url,c=n.userId,h=n.token,d=`${i}/${c}?token=${encodeURIComponent(h)}`,u=null!==this.currentBaseUrl||null!==this.currentUserId||null!==this.currentToken||null!==this.currentUrl,b=null!==this.currentBaseUrl&&this.currentBaseUrl!==i||null!==this.currentUserId&&this.currentUserId!==c||null!==this.currentToken&&this.currentToken!==h||null!==this.currentUrl&&this.currentUrl!==d;if(!u||b){if(u){const e={currentUserId:this.currentUserId??"",newUserId:c,message:`检测到连接身份/参数变化:oldUser=${this.currentUserId??"null"} -> newUser=${c}, oldBaseUrl=${this.currentBaseUrl??"null"} -> newBaseUrl=${i}, tokenChanged=${!this.currentToken||this.currentToken!==h}。将切换到最新登录态并重建连接。`};this.broadcastToAllTabs(o,e),console.warn("[SharedWorker]",e.message)}this.currentBaseUrl=i,this.currentUserId=c,this.currentToken=h,this.currentUrl=d,this.config=n.config,this.sharedWorkerIdleTimeout=n.sharedWorkerIdleTimeout??3e4,this.lastMessageByType.clear(),this.socket&&(console.log("[SharedWorker] 检测到登录态变化,断开旧连接以使用新参数"),this.disconnect())}else this.config=n.config,this.sharedWorkerIdleTimeout=n.sharedWorkerIdleTimeout??this.sharedWorkerIdleTimeout;this.socket&&this.socket.readyState===WebSocket.OPEN&&this.sendToTab(e,t,{}),this.checkAllTabsVisibility()}removeTab(e){this.tabs.delete(e),console.log(`[SharedWorker] 标签页已移除: ${e}, 剩余标签页数量: ${this.tabs.size}`),0===this.tabs.size?(console.log(`[SharedWorker] 所有标签页已关闭,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`),this.clearReconnectTimer(),this.stopTabCleanup(),this.startIdleTimer()):this.checkAllTabsVisibility()}updateTabVisibility(e,t){const s=this.tabs.get(e);s?(s.isVisible=t,s.lastSeen=Date.now(),console.log(`[SharedWorker] 标签页 ${e} 可见性更新: ${t}`),this.checkAllTabsVisibility()):console.warn(`[SharedWorker] 标签页不存在: ${e}`)}checkAllTabsVisibility(){if(0===this.tabs.size)return;Array.from(this.tabs.values()).every(e=>!e.isVisible)?(console.log(`[SharedWorker] 所有标签页都不可见,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`),this.clearReconnectTimer(),this.startIdleTimer()):(console.log("[SharedWorker] 至少有一个标签页可见,保持连接"),this.resetIdleTimer(),this.socket&&this.socket.readyState===WebSocket.OPEN||this.currentUrl&&(console.log("[SharedWorker] 检测到连接已断开,标签页可见,尝试重新连接"),this.connect()))}startIdleTimer(){this.clearIdleTimer(),this.idleTimer=globalThis.setTimeout(()=>{console.log("[SharedWorker] 空闲超时,断开连接"),this.disconnect()},this.sharedWorkerIdleTimeout)}resetIdleTimer(){this.clearIdleTimer()}clearIdleTimer(){null!==this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}connect(){if(this.currentUrl)if(Date.now()<this.reconnectSuppressedUntil)console.warn("[SharedWorker] 自动重连已熔断,暂不连接",{suppressedUntil:this.reconnectSuppressedUntil});else if(this.hasVisibleTab()){if(!this.socket||this.socket.readyState!==WebSocket.OPEN&&this.socket.readyState!==WebSocket.CONNECTING){this.manualClose=!1,this.clearReconnectTimer();try{this.socket=new WebSocket(this.currentUrl),this.socket.onopen=()=>{console.log("[SharedWorker] ✅ WebSocket 连接成功"),this.reconnectAttempts=0,this.lastOpenAt=Date.now(),this.fastClose1000Count=0,this.startHeartbeat(),console.log(`[SharedWorker] 通知 ${this.tabs.size} 个标签页: 已连接`),this.broadcastToAllTabs(t,{})},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{const t=Date.now(),o=this.lastOpenAt?t-this.lastOpenAt:-1;if(console.log("[SharedWorker] WebSocket 连接关闭",{code:e.code,reason:e.reason,wasClean:e.wasClean,liveMs:o}),this.stopHeartbeat(),this.broadcastToAllTabs(s,{}),1e3===e.code&&o>=0&&o<3e3){if(this.fastClose1000Count+=1,console.warn("[SharedWorker] 检测到快速 1000 关闭",{fastClose1000Count:this.fastClose1000Count,liveMs:o}),this.fastClose1000Count>=3){this.reconnectSuppressedUntil=t+6e4;const s={message:"WebSocket 被服务端频繁正常关闭(1000),已临时暂停自动重连 60s。请检查 token/服务端是否限制同账号多连接/是否需要额外鉴权消息。",error:{code:e.code,reason:e.reason,liveMs:o}};return void this.broadcastToAllTabs(r,s)}}else this.fastClose1000Count=0;!this.manualClose&&this.config?.autoReconnect&&this.tabs.size>0&&this.hasVisibleTab()&&this.scheduleReconnect()},this.socket.onerror=e=>{console.error("[SharedWorker] WebSocket 连接错误",e),this.stopHeartbeat();const t={message:"WebSocket 连接错误",error:e};this.broadcastToAllTabs(r,t)}}catch(e){console.error("[SharedWorker] 创建 WebSocket 连接失败",e);const t={message:"创建 WebSocket 连接失败",error:e};this.broadcastToAllTabs(r,t),this.config?.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}}else console.log("[SharedWorker] 当前无可见标签页,跳过连接");else console.error("[SharedWorker] 缺少 WebSocket URL")}scheduleReconnect(){if(0===this.tabs.size||!this.hasVisibleTab())return;const e=this.config?.maxReconnectAttempts??10,t=this.config?.reconnectDelay??3e3,s=this.config?.reconnectDelayMax??1e4;if(this.reconnectAttempts>=e||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=e&&console.warn("[SharedWorker] 已达到最大重连次数"));this.reconnectAttempts+=1;const r=Math.min(t*this.reconnectAttempts,s);console.log(`[SharedWorker] 将在 ${r}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.clearReconnectTimer(),this.reconnectTimer=globalThis.setTimeout(()=>{this.connect()},r)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.clearIdleTimer(),this.clearReconnectTimer(),this.socket&&(this.socket.close(),this.socket=null),this.reconnectAttempts=0}startHeartbeat(){this.stopHeartbeat();const e=this.config?.heartbeatInterval??25e3;this.heartbeatTimer=globalThis.setInterval(()=>{if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return;const e={type:"PING",timestamp:Date.now()};this.send(e)},e)}stopHeartbeat(){null!==this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void console.warn("[SharedWorker] WebSocket 未连接,无法发送消息");const t="string"==typeof e?e:JSON.stringify(e);this.socket.send(t)}handleIncoming(t){if(!t)return;let s;try{s=JSON.parse(t)}catch{return void console.warn("[SharedWorker] 无法解析消息",t)}if(!s?.type)return;console.log(`[SharedWorker] 📨 收到服务器消息, type: ${s.type}`,s);const r={data:t,message:s};this.lastMessageByType.set(s.type,r);let o=0;for(const t of this.tabs.values())console.log(`[SharedWorker] 检查标签页 ${t.tabId}, 注册的类型:`,Array.from(t.registeredTypes)),t.registeredTypes.has(s.type)&&(console.log(`[SharedWorker] ✅ 发送消息到标签页 ${t.tabId}, type: ${s.type}`),this.sendToTab(t.port,e,r),o++);console.log(`[SharedWorker] 消息分发完成, type: ${s.type}, 发送给 ${o} 个标签页`)}registerCallback(t,s){const r=this.tabs.get(t);if(!r)return void console.warn(`[SharedWorker] ⚠️ 标签页不存在: ${t}`);r.lastSeen=Date.now(),r.registeredTypes.add(s.type),r.callbackMap.set(s.callbackId,s.type),console.log(`[SharedWorker] ✅ 标签页 ${t} 注册回调: ${s.type} (${s.callbackId})`),console.log(`[SharedWorker] 标签页 ${t} 当前注册的所有类型:`,Array.from(r.registeredTypes));const o=this.lastMessageByType.get(s.type);o&&(console.log(`[SharedWorker] 🔁 回放缓存消息到标签页 ${t}, type: ${s.type}`),this.sendToTab(r.port,e,o))}unregisterCallback(e,t){const s=this.tabs.get(e);if(s)if(s.lastSeen=Date.now(),t.callbackId){const r=s.callbackMap.get(t.callbackId);if(r){s.callbackMap.delete(t.callbackId);Array.from(s.callbackMap.values()).some(e=>e===r)||s.registeredTypes.delete(r),console.log(`[SharedWorker] 标签页 ${e} 取消注册回调: ${r}`)}}else{s.registeredTypes.delete(t.type);for(const[e,r]of s.callbackMap.entries())r===t.type&&s.callbackMap.delete(e);console.log(`[SharedWorker] 标签页 ${e} 取消注册所有 ${t.type} 类型回调`)}else console.warn(`[SharedWorker] 标签页不存在: ${e}`)}sendToTab(e,t,s){const r={type:t,payload:s,timestamp:Date.now()};try{e.postMessage(r)}catch(e){console.error("[SharedWorker] 发送消息到标签页失败",e)}}broadcastToAllTabs(e,t){for(const s of this.tabs.values())this.sendToTab(s.port,e,t)}forceReset(e){console.warn("[SharedWorker] 🔄 收到强制重置指令,正在重置 Worker 状态",{reason:e}),this.disconnect(),this.lastMessageByType.clear(),this.currentUrl=null,this.currentBaseUrl=null,this.currentUserId=null,this.currentToken=null,this.broadcastToAllTabs(s,{}),console.log("[SharedWorker] ✅ Worker 状态已重置,等待新的连接参数")}forceShutdown(e){console.warn("[SharedWorker] ⚠️ 收到强制关闭指令,正在关闭 Worker",{reason:e}),this.disconnect(),this.lastMessageByType.clear(),this.currentUrl=null,this.currentBaseUrl=null,this.currentUserId=null,this.currentToken=null;for(const e of this.tabs.values()){try{e.port.postMessage({type:s,payload:{},timestamp:Date.now()})}catch{}try{e.port.close()}catch{}}this.tabs.clear(),this.stopTabCleanup();const t=globalThis.close;if("function"==typeof t){console.warn("[SharedWorker] 🛑 正在终止 SharedWorker 进程");try{t()}catch(e){console.warn("[SharedWorker] 终止 SharedWorker 失败",e)}}}handleNetworkOnline(){console.log("[SharedWorker] 🌐 收到网络恢复通知"),this.socket&&this.socket.readyState===WebSocket.OPEN?console.log("[SharedWorker] 已有活跃连接,无需重连"):(this.reconnectAttempts=0,this.clearReconnectTimer(),this.reconnectSuppressedUntil=0,this.currentUrl&&this.hasVisibleTab()?(console.log("[SharedWorker] 网络恢复,立即尝试重连"),this.connect()):console.log("[SharedWorker] 网络恢复,但无可见标签页或无 URL,等待条件满足"))}};globalThis.onconnect=e=>{const t=e.ports[0];t.onmessage=e=>{const s=e.data;console.log(`[SharedWorker] 📬 收到标签页消息, type: ${s.type}, tabId: ${s.tabId}`);const r=a.tabs?.get?.(s.tabId);switch(r&&(r.lastSeen=Date.now()),s.type){case"TAB_INIT":a.addTab(t,s);break;case"TAB_SEND":a.send(s.payload.data);break;case"TAB_VISIBILITY":a.updateTabVisibility(s.tabId,s.payload.isVisible);break;case"TAB_REGISTER_CALLBACK":console.log("[SharedWorker] 🔔 处理注册回调请求:",s.payload),a.registerCallback(s.tabId,s.payload);break;case"TAB_UNREGISTER_CALLBACK":a.unregisterCallback(s.tabId,s.payload);break;case"TAB_DISCONNECT":a.removeTab(s.tabId);break;case"TAB_PING":t.postMessage({type:"WORKER_PONG",timestamp:Date.now()});break;case"TAB_FORCE_RESET":a.forceReset(s.payload?.reason);break;case"TAB_FORCE_SHUTDOWN":a.forceShutdown(s.payload?.reason);break;case"TAB_NETWORK_ONLINE":a.handleNetworkOnline();break;default:console.warn("[SharedWorker] 未知消息类型",s.type)}},t.start()};
2
2
  //# sourceMappingURL=worker-script.esm.js.map