@brewer/dj-common 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -2,17 +2,18 @@
2
2
 
3
3
  多端通用的公共方法库,支持 PC、H5、APP 等平台。使用 TypeScript 编写,提供完整的类型支持。
4
4
 
5
- ## 特性
6
-
7
- - 📘 **TypeScript** - 完全使用 TypeScript 编写,提供完整的类型定义
8
- - 📦 **多格式支持** - 同时提供 ESM 和 CommonJS 两种格式
9
- - 🔌 **按需引入** - 支持独立引入模块,减小打包体积
10
- - 🔧 **可配置** - 所有参数都可灵活配置
11
- - 🔄 **自动重连** - 内置智能重连机制
12
- - 💓 **心跳检测** - 自动维持连接活性
13
- - 📝 **日志系统** - 内置可配置的日志系统,支持多级别控制
14
- - 🎯 **类型安全** - 完整的 TypeScript 类型支持
15
- - 🚀 **SharedWorker 支持** - 多标签页共享 WebSocket 连接,优化资源使用
5
+ ## 特性
6
+
7
+ - **TypeScript** - 完全使用 TypeScript 编写,提供完整的类型定义
8
+ - **多格式支持** - 同时提供 ESM 和 CommonJS 两种格式
9
+ - **按需引入** - 支持独立引入模块,减小打包体积
10
+ - **可配置** - 所有参数都可灵活配置
11
+ - **自动重连** - 内置智能重连机制
12
+ - **心跳检测** - 自动维持连接活性
13
+ - **日志系统** - 内置可配置的日志系统,支持多级别控制
14
+ - **类型安全** - 完整的 TypeScript 类型支持
15
+ - **SharedWorker 支持** - 多标签页共享 WebSocket 连接,优化资源使用
16
+ - **网络状态监听** - 网络恢复时自动重连
16
17
 
17
18
  ## 安装
18
19
 
@@ -73,9 +74,6 @@ npm install
73
74
  # 开发模式(监听文件变化)
74
75
  npm run dev
75
76
 
76
- # 类型检查
77
- npm run type-check
78
-
79
77
  # 代码检查
80
78
  npm run lint
81
79
 
@@ -88,11 +86,7 @@ npm run build
88
86
 
89
87
  ## 发布
90
88
 
91
- 使用claude code时 只需发送 "帮我发布一个新版本" 即可
92
-
93
- 推送消息必须要是:`chore: bump version to 1.0.0-beta.7`
94
-
95
- `> 验证通过了,你需要提交一个PR合并到main分支,提交的标题是chore: bump version to 1.0.0-beta.15 发布一个beta版本`
89
+ 使用 Claude Code 时只需发送 "帮我发布一个新版本" 即可自动完成发布流程。
96
90
 
97
91
  ```bash
98
92
  # 自动版本管理和发布
@@ -112,28 +106,43 @@ npm publish
112
106
  ```
113
107
  @brewer/dj-common
114
108
  ├── WebSocketClient (基础类)
115
- │ ├── 连接管理
116
- │ ├── 心跳检测
117
- │ ├── 自动重连
118
- │ ├── 消息回调
119
- │ ├── 日志系统
120
- └── 生命周期钩子
109
+ │ ├── 连接管理(connect/disconnect)
110
+ │ ├── 心跳检测(自动发送 PING)
111
+ │ ├── 自动重连(指数退避策略)
112
+ │ ├── 消息回调系统(type-based routing)
113
+ │ ├── 日志系统(可配置的日志级别)
114
+ ├── 网络状态监听(网络恢复时自动重连)
115
+ │ └── 生命周期钩子(onOpen/onClose/onError/onMessage)
121
116
 
122
- ├── MessageSocket (业务类)
123
- │ ├── 继承 WebSocketClient
124
- │ ├── 用户认证
125
- └── 消息管理
117
+ ├── MessageSocket (业务类 - 静态单例)
118
+ │ ├── 用户认证(userId + token)
119
+ │ ├── URL 构建
120
+ ├── 全局消息管理
121
+ │ ├── 三种连接模式(sharedWorker / visibility / normal)
122
+ │ └── 自动降级策略
123
+
124
+ ├── SharedWorkerManager (标签页端管理器)
125
+ │ ├── 创建和管理 SharedWorker
126
+ │ ├── MessagePort 通信
127
+ │ ├── 页面可见性监听
128
+ │ └── 网络状态监听
129
+
130
+ ├── worker-script (SharedWorker 脚本)
131
+ │ ├── 管理跨标签页共享的 WebSocket 连接
132
+ │ ├── 消息广播
133
+ │ └── 空闲超时管理
126
134
 
127
135
  └── Logger (日志类)
128
136
  ├── 多级别日志(debug/info/warn/error/silent)
129
- ├── 可配置日志级别
130
- └── 带名称前缀
137
+ ├── 优先级控制
138
+ └── 带名称前缀的日志输出
131
139
  ```
132
140
 
133
141
  **设计理念:**
134
142
 
135
143
  - `WebSocketClient` 是通用的 WebSocket 基础封装,不依赖具体业务
136
144
  - `MessageSocket` 基于 `WebSocketClient`,添加用户认证等业务功能
145
+ - `SharedWorkerManager` 管理 SharedWorker 连接,实现跨标签页共享
137
146
  - `Logger` 提供统一的日志管理,支持多级别控制
138
147
  - 职责分离,易于扩展和维护
139
148
 
@@ -166,7 +175,7 @@ const client = new WebSocketClient({
166
175
  import { MessageSocket } from '@brewer/dj-common'
167
176
 
168
177
  MessageSocket.setConfig({
169
- url: 'ws://localhost:8080',
178
+ url: 'ws://localhost:8080/api/websocket/messageServer',
170
179
  logLevel: 'info', // 设置日志级别
171
180
  })
172
181
  ```
@@ -201,7 +210,7 @@ logger.setLevel('warn')
201
210
  import { MessageSocket } from '@brewer/dj-common'
202
211
 
203
212
  MessageSocket.setConfig({
204
- url: 'ws://localhost:8080',
213
+ url: 'wss://your-server.com/api/websocket/messageServer',
205
214
  // connectionMode: 'auto', // 默认值,自动启用 SharedWorker
206
215
  })
207
216
 
@@ -213,11 +222,11 @@ MessageSocket.start({
213
222
 
214
223
  **优势:**
215
224
 
216
- - 所有标签页共享一个 WebSocket 连接
217
- - 任意标签页可见时保持连接,避免频繁断连
218
- - 所有标签页不可见时等待 30 秒才断开(可配置)
219
- - 节省服务器资源,减少连接数
220
- - 用户体验流畅,切换标签页不会中断连接
225
+ - 所有标签页共享一个 WebSocket 连接
226
+ - 任意标签页可见时保持连接,避免频繁断连
227
+ - 所有标签页不可见时等待 30 秒才断开(可配置)
228
+ - 节省服务器资源,减少连接数
229
+ - 用户体验流畅,切换标签页不会中断连接
221
230
 
222
231
  **空闲超时配置:**
223
232
 
@@ -233,7 +242,7 @@ MessageSocket.setConfig({
233
242
 
234
243
  ```typescript
235
244
  MessageSocket.setConfig({
236
- url: 'ws://localhost:8080',
245
+ url: 'wss://your-server.com/api/websocket/messageServer',
237
246
  connectionMode: 'visibility', // 显式使用 Visibility 模式
238
247
  enableVisibilityManagement: true, // 必须启用
239
248
  })
@@ -250,7 +259,7 @@ MessageSocket.setConfig({
250
259
 
251
260
  ```typescript
252
261
  MessageSocket.setConfig({
253
- url: 'ws://localhost:8080',
262
+ url: 'wss://your-server.com/api/websocket/messageServer',
254
263
  connectionMode: 'normal', // 显式使用 Normal 模式
255
264
  })
256
265
  ```
@@ -259,9 +268,9 @@ MessageSocket.setConfig({
259
268
 
260
269
  | 模式 | 连接数 | 切换标签页 | 后台接收消息 | 浏览器兼容性 |
261
270
  | ------------ | ------ | ---------- | ------------ | --------------------------- |
262
- | SharedWorker | 1 个 | 保持连接 | | Chrome, Firefox, Edge |
263
- | Visibility | 1 个 | 断开/重连 | | 所有支持 Visibility API |
264
- | Normal | N 个 | 保持连接 | | 所有支持 WebSocket 的浏览器 |
271
+ | SharedWorker | 1 个 | 保持连接 | | Chrome, Firefox, Edge |
272
+ | Visibility | 1 个 | 断开/重连 | | 所有支持 Visibility API |
273
+ | Normal | N 个 | 保持连接 | | 所有支持 WebSocket 的浏览器 |
265
274
 
266
275
  ### 查看当前模式
267
276
 
@@ -272,24 +281,45 @@ console.log('当前连接模式:', mode) // 'sharedWorker' | 'visibility' | 'nor
272
281
 
273
282
  **适用场景:**
274
283
 
275
- - 用户可能打开多个标签页的应用
276
- - 需要优化服务器连接数的场景
277
- - 移动端 WebView 应用(页面切换到后台)
278
- - ❌ 需要在后台持续接收消息且不支持 SharedWorker 的场景
284
+ - 用户可能打开多个标签页的应用
285
+ - 需要优化服务器连接数的场景
286
+ - 移动端 WebView 应用(页面切换到后台)
287
+
288
+ ## 网络状态监听
289
+
290
+ 库内置了网络状态监听功能,解决移动端断网较久后无法自动重连的问题:
291
+
292
+ - 监听 `online`/`offline` 事件
293
+ - 网络恢复时自动重置重连计数并立即重连
294
+ - 默认启用,可通过 `enableNetworkListener: false` 关闭
295
+
296
+ ```typescript
297
+ // WebSocketClient
298
+ const client = new WebSocketClient({
299
+ url: 'ws://localhost:8080',
300
+ enableNetworkListener: true, // 默认 true
301
+ })
302
+
303
+ // MessageSocket(通过 SharedWorker 或直接连接都支持)
304
+ MessageSocket.setConfig({
305
+ url: 'wss://your-server.com/api/websocket/messageServer',
306
+ // enableNetworkListener 默认为 true
307
+ })
308
+ ```
279
309
 
280
310
  ## 本地测试
281
311
 
282
- ```ts
312
+ ```bash
283
313
  npm link
284
314
  # 在包中
285
315
  pnpm unlink --global
286
316
  pnpm link --global && echo "===== 重新 link 完成 ====="
287
- // 使用处
317
+ # 使用处
288
318
  pnpm unlink @brewer/dj-common
289
319
  pnpm link --global @brewer/dj-common
290
320
  ```
291
321
 
292
- ### worker测试
322
+ ### Worker 测试
293
323
 
294
324
  `chrome://inspect/#workers`
295
325
 
@@ -299,4 +329,4 @@ MIT
299
329
 
300
330
  ---
301
331
 
302
- **Made with ❤️ by BeerUi**
332
+ **Made with BeerUi**
@@ -1,2 +1,2 @@
1
- "use strict";const e={debug:10,info:20,warn:30,error:40,silent:50};class t{constructor(e,t="warn"){this.name=e,this.level=t}setLevel(e){this.level=e}getLevel(){return this.level}debug(...e){this.logAtLevel("debug",console.debug,e)}info(...e){this.logAtLevel("info",console.info,e)}warn(...e){this.logAtLevel("warn",console.warn,e)}error(...e){this.logAtLevel("error",console.error,e)}shouldLog(t){return"silent"!==t&&e[t]>=e[this.level]}logAtLevel(e,t,n){this.shouldLog(e)&&t(`[${this.name}]`,...n)}}class n{constructor(e={}){this.socket=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.callbackList=[],this.currentUrl=null,this.reconnectAttempts=0,this.manualClose=!1,this.networkListenerInitialized=!1,this.handleOnline=()=>{this.logger.info("[WebSocketClient] 网络已恢复"),this.manualClose||this.isConnected()||(this.reconnectAttempts=0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.currentUrl&&this.config.autoReconnect&&(this.logger.info("[WebSocketClient] 网络恢复,立即尝试重连"),this.connect(this.currentUrl)))},this.handleOffline=()=>{this.logger.info("[WebSocketClient] 网络已断开"),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null)},this.config={...n.DEFAULT_CONFIG,...e},this.logger=new t("WebSocketClient",this.config.logLevel)}updateConfig(e){this.config={...this.config,...e},this.logger.setLevel(this.config.logLevel??n.DEFAULT_CONFIG.logLevel)}connect(e){const t=e||this.config.url;if(t){this.currentUrl=t,this.manualClose=!1,this.initNetworkListener();try{this.socket=new WebSocket(t),this.socket.onopen=()=>{this.logger.info("[WebSocketClient] 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),this.onOpen()},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{this.logger.info("[WebSocketClient] 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.onClose(e),!this.manualClose&&this.config.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{this.logger.error("[WebSocketClient] 连接错误",e),this.stopHeartbeat(),this.onError(e)}}catch(e){this.logger.error("[WebSocketClient] 连接失败",e),this.config.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else this.logger.error("[WebSocketClient] 缺少 WebSocket URL")}scheduleReconnect(){if(this.reconnectAttempts>=this.config.maxReconnectAttempts||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=this.config.maxReconnectAttempts&&this.logger.warn("[WebSocketClient] 已达到最大重连次数"));this.reconnectAttempts+=1;const e=Math.min(this.config.reconnectDelay*this.reconnectAttempts,this.config.reconnectDelayMax);this.logger.debug(`[WebSocketClient] 将在 ${e}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=window.setTimeout(()=>{this.connect(this.currentUrl)},e)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.removeNetworkListener(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.currentUrl=null,this.reconnectAttempts=0}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void this.logger.warn("[WebSocketClient] WebSocket 未连接,无法发送消息");const t="string"==typeof e?e:JSON.stringify(e);this.socket.send(t)}handleIncoming(e){if(!e)return;let t;try{t=JSON.parse(e)}catch{return void this.logger.warn("[WebSocketClient] 无法解析消息",e)}if(!t?.type)return;this.callbackList.filter(e=>e.type===t.type).forEach(({callback:e})=>{try{e(t.data,t)}catch(e){this.logger.error("[WebSocketClient] 回调执行失败",e)}}),this.onMessage(t)}startHeartbeat(){this.stopHeartbeat(),this.socket&&this.socket.readyState===WebSocket.OPEN&&(this.heartbeatTimer=window.setInterval(()=>{if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return;const e=this.config.heartbeatMessage();this.send(e)},this.config.heartbeatInterval))}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}on(e,t){const n="string"==typeof e?{type:e,callback:t}:e;n.type&&"function"==typeof n.callback?this.callbackList.push(n):this.logger.warn("[WebSocketClient] 无效的回调配置",n)}off(e,t){this.callbackList=t?this.callbackList.filter(n=>!(n.type===e&&n.callback===t)):this.callbackList.filter(t=>t.type!==e)}clearCallbacks(){this.callbackList=[]}getReadyState(){return this.socket?.readyState??WebSocket.CLOSED}isConnected(){return this.socket?.readyState===WebSocket.OPEN}onOpen(){this.logger.info("[WebSocketClient] 连接打开")}onClose(e){this.logger.info("[WebSocketClient] 连接打开")}onError(e){this.logger.error("[WebSocketClient] 连接错误",e)}onMessage(e){this.logger.debug("[WebSocketClient] 收到消息",e)}initNetworkListener(){"undefined"==typeof window||this.networkListenerInitialized||this.config.enableNetworkListener&&(window.addEventListener("online",this.handleOnline),window.addEventListener("offline",this.handleOffline),this.networkListenerInitialized=!0,this.logger.debug("[WebSocketClient] 已初始化网络状态监听"))}removeNetworkListener(){"undefined"!=typeof window&&this.networkListenerInitialized&&(window.removeEventListener("online",this.handleOnline),window.removeEventListener("offline",this.handleOffline),this.networkListenerInitialized=!1,this.logger.debug("[WebSocketClient] 已移除网络状态监听"))}}n.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,heartbeatMessage:()=>({type:"PING",timestamp:Date.now()}),logLevel:"warn",enableNetworkListener:!0};class r{constructor(e){this.worker=null,this.port=null,this.callbacks=new Map,this.callbackIdCounter=0,this.connected=!1,this.visibilityListenerInitialized=!1,this.onConnectedCallback=null,this.onDisconnectedCallback=null,this.onErrorCallback=null,this.onAuthConflictCallback=null,this.pingTimer=null,this.unloadListenerInitialized=!1,this.networkListenerInitialized=!1,this.handleVisibilityChange=()=>{const e=!document.hidden;if(this.logger.debug(`[SharedWorkerManager] 页面可见性变化: ${e}`),this.port){const t={isVisible:e};this.sendToWorker("TAB_VISIBILITY",t)}},this.handlePageHide=()=>{this.port&&this.sendToWorker("TAB_DISCONNECT",{})},this.handleOnline=()=>{this.logger.info("[SharedWorkerManager] 网络已恢复,通知 Worker 重连"),this.port&&this.sendToWorker("TAB_NETWORK_ONLINE",{})},this.handleOffline=()=>{this.logger.info("[SharedWorkerManager] 网络已断开")},this.config=e,this.tabId=this.generateTabId(),this.logger=new t("SharedWorkerManager",e.logLevel??"warn")}generateTabId(){return`tab_${Date.now()}_${Math.random().toString(36).substring(2,9)}`}async sendResetToExistingWorker(e){try{const t=new SharedWorker(e,{name:r.WORKER_NAME}).port;t.start(),t.postMessage({type:"TAB_FORCE_RESET",payload:{reason:"force_new_start"},tabId:this.tabId,timestamp:Date.now()}),await new Promise(e=>setTimeout(e,100)),t.close(),this.logger.debug("[SharedWorkerManager] 已发送重置命令到现有 Worker")}catch(e){this.logger.warn("[SharedWorkerManager] 发送重置命令失败(可忽略)",e)}}async start(){try{this.logger.debug("[SharedWorkerManager] 开始启动 SharedWorker");const e=this.getWorkerScriptDataUrl();this.logger.debug(`[SharedWorkerManager] Worker Script URL 创建成功: ${e.slice(0,60)}...`),this.config.forceNewWorkerOnStart&&(this.logger.debug("[SharedWorkerManager] forceNewWorkerOnStart=true,发送重置命令"),await this.sendResetToExistingWorker(e)),this.logger.debug("[SharedWorkerManager] 正在创建 SharedWorker 实例..."),this.worker=new SharedWorker(e,{name:r.WORKER_NAME}),this.logger.debug("[SharedWorkerManager] ✅ SharedWorker 实例创建成功"),this.port=this.worker.port,this.port.onmessage=this.handleWorkerMessage.bind(this),this.port.start(),this.logger.debug("[SharedWorkerManager] ✅ MessagePort 已启动"),this.setupVisibilityListener(),this.setupUnloadListener(),this.setupNetworkListener();const t={heartbeatInterval:this.config.config.heartbeatInterval,maxReconnectAttempts:this.config.config.maxReconnectAttempts,reconnectDelay:this.config.config.reconnectDelay,reconnectDelayMax:this.config.config.reconnectDelayMax,autoReconnect:this.config.config.autoReconnect,logLevel:this.config.config.logLevel};return this.sendToWorker("TAB_INIT",{url:this.config.url,userId:this.config.userId,token:this.config.token,isVisible:!document.hidden,config:t,sharedWorkerIdleTimeout:this.config.sharedWorkerIdleTimeout}),this.startPing(),this.logger.info("[SharedWorkerManager] SharedWorker 已启动"),!0}catch(e){return this.logger.error("[SharedWorkerManager] 启动 SharedWorker 失败",e),!1}}stop(){this.logger.debug("[SharedWorkerManager] 停止当前标签页的 SharedWorker 连接");const e=this.port;this.port&&this.sendToWorker("TAB_DISCONNECT",{}),this.stopPing(),this.removeVisibilityListener(),this.removeUnloadListener(),this.removeNetworkListener(),this.port=null,this.worker=null,this.connected=!1,this.callbacks.clear(),e&&setTimeout(()=>{try{e.close()}catch{}},100)}forceShutdown(){this.logger.debug("[SharedWorkerManager] 强制关闭 Worker(退出登录)");const e=this.port;this.port&&this.sendToWorker("TAB_FORCE_SHUTDOWN",{reason:"logout"}),this.stopPing(),this.removeVisibilityListener(),this.removeUnloadListener(),this.removeNetworkListener(),this.port=null,this.worker=null,this.connected=!1,this.callbacks.clear(),e&&setTimeout(()=>{try{e.close()}catch{}},100)}send(e){if(!this.port)return void this.logger.warn("[SharedWorkerManager] MessagePort 未初始化,无法发送消息");const t={data:e};this.sendToWorker("TAB_SEND",t)}registerCallback(e){const t="callback_"+this.callbackIdCounter++,n={...e,id:t};if(this.callbacks.set(t,n),this.port){const n={type:e.type,callbackId:t};this.sendToWorker("TAB_REGISTER_CALLBACK",n),this.logger.debug(`[SharedWorkerManager] ✅ 已发送注册消息到 Worker: ${e.type} (${t})`)}else this.logger.warn(`[SharedWorkerManager] ⚠️ port 未初始化,无法发送注册消息: ${e.type}`);return this.logger.debug(`[SharedWorkerManager] 注册回调: ${e.type} (${t}), 当前回调总数: ${this.callbacks.size}`),t}unregisterCallback(e,t){if(t){let n=null;for(const[r,s]of this.callbacks.entries())if(s.type===e&&s.callback===t){n=r,this.callbacks.delete(r);break}if(n&&this.port){const t={type:e,callbackId:n};this.sendToWorker("TAB_UNREGISTER_CALLBACK",t)}this.logger.debug(`[SharedWorkerManager] 取消注册回调: ${e} (${n})`)}else{const t=[];for(const[n,r]of this.callbacks.entries())r.type===e&&(t.push(n),this.callbacks.delete(n));if(this.port){const t={type:e};this.sendToWorker("TAB_UNREGISTER_CALLBACK",t)}this.logger.debug(`[SharedWorkerManager] 取消注册所有 ${e} 类型回调 (${t.length} 个)`)}}clearCallbacks(){for(const e of this.callbacks.values())if(this.port){const t={type:e.type,callbackId:e.id};this.sendToWorker("TAB_UNREGISTER_CALLBACK",t)}this.callbacks.clear(),this.logger.debug("[SharedWorkerManager] 已清空所有回调")}isConnected(){return this.connected}onConnected(e){this.onConnectedCallback=e}onDisconnected(e){this.onDisconnectedCallback=e}onError(e){this.onErrorCallback=e}onAuthConflict(e){this.onAuthConflictCallback=e}handleWorkerMessage(e){const t=e.data;switch(this.logger.debug(`[SharedWorkerManager] 📬 收到 Worker 消息, type: ${t.type}`),t.type){case"WORKER_CONNECTED":this.connected=!0,this.logger.info("[SharedWorkerManager] ✅ WebSocket 已连接"),this.onConnectedCallback?.();break;case"WORKER_DISCONNECTED":this.connected=!1,this.logger.info("[SharedWorkerManager] WebSocket 已断开"),this.onDisconnectedCallback?.();break;case"WORKER_MESSAGE":try{const e=t.payload;console.log("[SharedWorkerManager] 📨 收到服务器消息(经 Worker 转发):",e?.message),this.logger.info("[SharedWorkerManager] 📨 收到服务器消息(经 Worker 转发)",e?.message),this.logger.debug("[SharedWorkerManager] 🧾 原始消息 data:",e?.data)}catch(e){this.logger.warn("[SharedWorkerManager] 打印服务器消息失败",e)}this.handleServerMessage(t.payload);break;case"WORKER_ERROR":this.logger.error("[SharedWorkerManager] Worker 错误",t.payload),this.onErrorCallback?.(t.payload);break;case"WORKER_AUTH_CONFLICT":this.logger.warn("[SharedWorkerManager] 身份冲突",t.payload),this.onAuthConflictCallback?.(t.payload);break;case"WORKER_PONG":this.logger.debug("[SharedWorkerManager] 收到 PONG");break;default:this.logger.warn("[SharedWorkerManager] 未知消息类型",t.type)}}handleServerMessage(e){const{message:t}=e;this.logger.debug(`[SharedWorkerManager] 📨 收到服务器消息, type: ${t.type}`),this.logger.debug("[SharedWorkerManager] 当前注册的回调类型:",Array.from(this.callbacks.values()).map(e=>e.type));let n=0;for(const e of this.callbacks.values())if(e.type===t.type){n++,this.logger.debug(`[SharedWorkerManager] ✅ 匹配到回调 ${e.type} (${e.id}),准备执行`);try{e.callback(t.data,t),this.logger.debug(`[SharedWorkerManager] ✅ 回调执行成功 ${e.type} (${e.id})`)}catch(e){this.logger.error("[SharedWorkerManager] ❌ 回调执行失败",e)}}0===n&&this.logger.warn(`[SharedWorkerManager] ⚠️ 没有匹配的回调: ${t.type}`)}sendToWorker(e,t){if(!this.port)return void this.logger.warn("[SharedWorkerManager] MessagePort 未初始化,无法发送消息");const n={type:e,payload:t,tabId:this.tabId,timestamp:Date.now()};try{this.port.postMessage(n)}catch(e){this.logger.error("[SharedWorkerManager] 发送消息到 Worker 失败",e)}}setupVisibilityListener(){"undefined"==typeof document||this.visibilityListenerInitialized||(document.addEventListener("visibilitychange",this.handleVisibilityChange),this.visibilityListenerInitialized=!0,this.logger.debug("[SharedWorkerManager] 已设置页面可见性监听"))}removeVisibilityListener(){"undefined"!=typeof document&&this.visibilityListenerInitialized&&(document.removeEventListener("visibilitychange",this.handleVisibilityChange),this.visibilityListenerInitialized=!1,this.logger.debug("[SharedWorkerManager] 已移除页面可见性监听"))}startPing(){this.stopPing(),"undefined"!=typeof window&&this.port&&(this.pingTimer=globalThis.setInterval(()=>{this.port&&this.sendToWorker("TAB_PING",{})},1e4))}stopPing(){null!==this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null)}setupUnloadListener(){"undefined"==typeof window||this.unloadListenerInitialized||(window.addEventListener("pagehide",this.handlePageHide,{capture:!0}),window.addEventListener("beforeunload",this.handlePageHide,{capture:!0}),this.unloadListenerInitialized=!0,this.logger.debug("[SharedWorkerManager] 已设置页面卸载监听"))}removeUnloadListener(){"undefined"!=typeof window&&this.unloadListenerInitialized&&(window.removeEventListener("pagehide",this.handlePageHide,{capture:!0}),window.removeEventListener("beforeunload",this.handlePageHide,{capture:!0}),this.unloadListenerInitialized=!1,this.logger.debug("[SharedWorkerManager] 已移除页面卸载监听"))}setupNetworkListener(){"undefined"==typeof window||this.networkListenerInitialized||(window.addEventListener("online",this.handleOnline),window.addEventListener("offline",this.handleOffline),this.networkListenerInitialized=!0,this.logger.debug("[SharedWorkerManager] 已设置网络状态监听"))}removeNetworkListener(){"undefined"!=typeof window&&this.networkListenerInitialized&&(window.removeEventListener("online",this.handleOnline),window.removeEventListener("offline",this.handleOffline),this.networkListenerInitialized=!1,this.logger.debug("[SharedWorkerManager] 已移除网络状态监听"))}getWorkerScriptDataUrl(){const e=this.getWorkerScriptContent();this.logger.debug(`[SharedWorkerManager] 正在创建 Worker Data URL, 代码长度: ${e.length}`);return`data:application/javascript;charset=utf-8;base64,${this.toBase64Utf8(e)}`}toBase64Utf8(e){try{if("undefined"!=typeof TextEncoder){const t=(new TextEncoder).encode(e);let n="";for(let e=0;e<t.length;e++)n+=String.fromCharCode(t[e]);return btoa(n)}return btoa(unescape(encodeURIComponent(e)))}catch(e){throw this.logger.error("[SharedWorkerManager] ❌ Worker 脚本 base64 编码失败",e),e}}getWorkerScriptContent(){const e="const WorkerToTabMessageType = {\n WORKER_READY: 'WORKER_READY',\n WORKER_MESSAGE: 'WORKER_MESSAGE',\n WORKER_CONNECTED: 'WORKER_CONNECTED',\n WORKER_DISCONNECTED: 'WORKER_DISCONNECTED',\n WORKER_ERROR: 'WORKER_ERROR',\n WORKER_AUTH_CONFLICT: 'WORKER_AUTH_CONFLICT',\n WORKER_PONG: 'WORKER_PONG',\n};\nclass WebSocketManager {\n constructor() {\n this.tabs = new Map();\n this.socket = null;\n this.lastMessageByType = new Map();\n this.tabCleanupTimer = null;\n this.idleTimer = null;\n this.heartbeatTimer = null;\n this.reconnectTimer = null;\n this.reconnectAttempts = 0;\n this.manualClose = false;\n this.currentUrl = null;\n this.currentUserId = null;\n this.currentToken = null;\n this.currentBaseUrl = null;\n this.lastOpenAt = 0;\n this.fastClose1000Count = 0;\n this.reconnectSuppressedUntil = 0;\n this.config = null;\n this.sharedWorkerIdleTimeout = 30000;\n this.tabStaleTimeout = 45000;\n }\n hasVisibleTab() {\n return Array.from(this.tabs.values()).some((tab) => tab.isVisible);\n }\n clearReconnectTimer() {\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n startTabCleanup() {\n if (this.tabCleanupTimer !== null)\n return;\n this.tabCleanupTimer = globalThis.setInterval(() => {\n const now = Date.now();\n const staleTabIds = [];\n for (const tab of this.tabs.values()) {\n if (now - tab.lastSeen > this.tabStaleTimeout) {\n staleTabIds.push(tab.tabId);\n }\n }\n if (staleTabIds.length > 0) {\n console.warn('[SharedWorker] 检测到过期标签页,将清理:', staleTabIds);\n staleTabIds.forEach((id) => this.removeTab(id));\n }\n }, 15000);\n }\n stopTabCleanup() {\n if (this.tabCleanupTimer !== null) {\n clearInterval(this.tabCleanupTimer);\n this.tabCleanupTimer = null;\n }\n }\n addTab(port, message) {\n const { tabId, payload } = message;\n const initPayload = payload;\n if (this.currentUserId && this.currentUserId !== initPayload.userId) {\n const conflictPayload = {\n currentUserId: this.currentUserId,\n newUserId: initPayload.userId,\n message: `检测到不同用户身份:当前连接用户为 ${this.currentUserId},新标签页尝试使用用户 ${initPayload.userId} 连接。将复用现有连接。`,\n };\n this.sendToTab(port, WorkerToTabMessageType.WORKER_AUTH_CONFLICT, conflictPayload);\n console.warn('[SharedWorker]', conflictPayload.message);\n }\n const tabInfo = {\n port,\n tabId,\n isVisible: initPayload.isVisible,\n lastSeen: Date.now(),\n registeredTypes: new Set(),\n callbackMap: new Map(),\n };\n this.tabs.set(tabId, tabInfo);\n console.log(`[SharedWorker] 标签页已添加: ${tabId}, 当前标签页数量: ${this.tabs.size}`);\n this.startTabCleanup();\n const nextBaseUrl = initPayload.url;\n const nextUserId = initPayload.userId;\n const nextToken = initPayload.token;\n const nextUrl = `${nextBaseUrl}/${nextUserId}?token=${encodeURIComponent(nextToken)}`;\n const hasExistingIdentity = this.currentBaseUrl !== null ||\n this.currentUserId !== null ||\n this.currentToken !== null ||\n this.currentUrl !== null;\n const identityChanged = (this.currentBaseUrl !== null && this.currentBaseUrl !== nextBaseUrl) ||\n (this.currentUserId !== null && this.currentUserId !== nextUserId) ||\n (this.currentToken !== null && this.currentToken !== nextToken) ||\n (this.currentUrl !== null && this.currentUrl !== nextUrl);\n if (!hasExistingIdentity || identityChanged) {\n if (hasExistingIdentity) {\n const conflictPayload = {\n currentUserId: this.currentUserId ?? '',\n newUserId: nextUserId,\n message: `检测到连接身份/参数变化:` +\n `oldUser=${this.currentUserId ?? 'null'} -> newUser=${nextUserId}, ` +\n `oldBaseUrl=${this.currentBaseUrl ?? 'null'} -> newBaseUrl=${nextBaseUrl}, ` +\n `tokenChanged=${this.currentToken ? this.currentToken !== nextToken : true}。` +\n `将切换到最新登录态并重建连接。`,\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_AUTH_CONFLICT, conflictPayload);\n console.warn('[SharedWorker]', conflictPayload.message);\n }\n this.currentBaseUrl = nextBaseUrl;\n this.currentUserId = nextUserId;\n this.currentToken = nextToken;\n this.currentUrl = nextUrl;\n this.config = initPayload.config;\n this.sharedWorkerIdleTimeout = initPayload.sharedWorkerIdleTimeout ?? 30000;\n this.lastMessageByType.clear();\n if (this.socket) {\n console.log('[SharedWorker] 检测到登录态变化,断开旧连接以使用新参数');\n this.disconnect();\n }\n }\n else {\n this.config = initPayload.config;\n this.sharedWorkerIdleTimeout = initPayload.sharedWorkerIdleTimeout ?? this.sharedWorkerIdleTimeout;\n }\n if (this.socket && this.socket.readyState === WebSocket.OPEN) {\n this.sendToTab(port, WorkerToTabMessageType.WORKER_CONNECTED, {});\n }\n this.checkAllTabsVisibility();\n }\n removeTab(tabId) {\n this.tabs.delete(tabId);\n console.log(`[SharedWorker] 标签页已移除: ${tabId}, 剩余标签页数量: ${this.tabs.size}`);\n if (this.tabs.size === 0) {\n console.log(`[SharedWorker] 所有标签页已关闭,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`);\n this.clearReconnectTimer();\n this.stopTabCleanup();\n this.startIdleTimer();\n }\n else {\n this.checkAllTabsVisibility();\n }\n }\n updateTabVisibility(tabId, isVisible) {\n const tab = this.tabs.get(tabId);\n if (!tab) {\n console.warn(`[SharedWorker] 标签页不存在: ${tabId}`);\n return;\n }\n tab.isVisible = isVisible;\n tab.lastSeen = Date.now();\n console.log(`[SharedWorker] 标签页 ${tabId} 可见性更新: ${isVisible}`);\n this.checkAllTabsVisibility();\n }\n checkAllTabsVisibility() {\n if (this.tabs.size === 0)\n return;\n const allHidden = Array.from(this.tabs.values()).every((tab) => !tab.isVisible);\n if (allHidden) {\n console.log(`[SharedWorker] 所有标签页都不可见,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`);\n this.clearReconnectTimer();\n this.startIdleTimer();\n }\n else {\n console.log('[SharedWorker] 至少有一个标签页可见,保持连接');\n this.resetIdleTimer();\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n if (this.currentUrl) {\n console.log('[SharedWorker] 检测到连接已断开,标签页可见,尝试重新连接');\n this.connect();\n }\n }\n }\n }\n startIdleTimer() {\n this.clearIdleTimer();\n this.idleTimer = globalThis.setTimeout(() => {\n console.log('[SharedWorker] 空闲超时,断开连接');\n this.disconnect();\n }, this.sharedWorkerIdleTimeout);\n }\n resetIdleTimer() {\n this.clearIdleTimer();\n }\n clearIdleTimer() {\n if (this.idleTimer !== null) {\n clearTimeout(this.idleTimer);\n this.idleTimer = null;\n }\n }\n connect() {\n if (!this.currentUrl) {\n console.error('[SharedWorker] 缺少 WebSocket URL');\n return;\n }\n if (Date.now() < this.reconnectSuppressedUntil) {\n console.warn('[SharedWorker] 自动重连已熔断,暂不连接', {\n suppressedUntil: this.reconnectSuppressedUntil,\n });\n return;\n }\n if (!this.hasVisibleTab()) {\n console.log('[SharedWorker] 当前无可见标签页,跳过连接');\n return;\n }\n if (this.socket && (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING)) {\n return;\n }\n this.manualClose = false;\n this.clearReconnectTimer();\n try {\n this.socket = new WebSocket(this.currentUrl);\n this.socket.onopen = () => {\n console.log('[SharedWorker] ✅ WebSocket 连接成功');\n this.reconnectAttempts = 0;\n this.lastOpenAt = Date.now();\n this.fastClose1000Count = 0;\n this.startHeartbeat();\n console.log(`[SharedWorker] 通知 ${this.tabs.size} 个标签页: 已连接`);\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_CONNECTED, {});\n };\n this.socket.onmessage = (event) => {\n this.handleIncoming(event.data);\n };\n this.socket.onclose = (event) => {\n const now = Date.now();\n const liveMs = this.lastOpenAt ? now - this.lastOpenAt : -1;\n console.log('[SharedWorker] WebSocket 连接关闭', {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean,\n liveMs,\n });\n this.stopHeartbeat();\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_DISCONNECTED, {});\n if (event.code === 1000 && liveMs >= 0 && liveMs < 3000) {\n this.fastClose1000Count += 1;\n console.warn('[SharedWorker] 检测到快速 1000 关闭', {\n fastClose1000Count: this.fastClose1000Count,\n liveMs,\n });\n if (this.fastClose1000Count >= 3) {\n this.reconnectSuppressedUntil = now + 60000;\n const errorPayload = {\n message: 'WebSocket 被服务端频繁正常关闭(1000),已临时暂停自动重连 60s。请检查 token/服务端是否限制同账号多连接/是否需要额外鉴权消息。',\n error: {\n code: event.code,\n reason: event.reason,\n liveMs,\n },\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload);\n return;\n }\n }\n else {\n this.fastClose1000Count = 0;\n }\n if (!this.manualClose && this.config?.autoReconnect && this.tabs.size > 0 && this.hasVisibleTab()) {\n this.scheduleReconnect();\n }\n };\n this.socket.onerror = (event) => {\n console.error('[SharedWorker] WebSocket 连接错误', event);\n this.stopHeartbeat();\n const errorPayload = {\n message: 'WebSocket 连接错误',\n error: event,\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload);\n };\n }\n catch (error) {\n console.error('[SharedWorker] 创建 WebSocket 连接失败', error);\n const errorPayload = {\n message: '创建 WebSocket 连接失败',\n error,\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload);\n if (this.config?.autoReconnect && !this.manualClose) {\n this.scheduleReconnect();\n }\n }\n }\n scheduleReconnect() {\n if (this.tabs.size === 0 || !this.hasVisibleTab()) {\n return;\n }\n const maxAttempts = this.config?.maxReconnectAttempts ?? 10;\n const reconnectDelay = this.config?.reconnectDelay ?? 3000;\n const reconnectDelayMax = this.config?.reconnectDelayMax ?? 10000;\n if (this.reconnectAttempts >= maxAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= maxAttempts) {\n console.warn('[SharedWorker] 已达到最大重连次数');\n }\n return;\n }\n this.reconnectAttempts += 1;\n const delay = Math.min(reconnectDelay * this.reconnectAttempts, reconnectDelayMax);\n console.log(`[SharedWorker] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`);\n this.clearReconnectTimer();\n this.reconnectTimer = globalThis.setTimeout(() => {\n this.connect();\n }, delay);\n }\n disconnect() {\n this.manualClose = true;\n this.stopHeartbeat();\n this.clearIdleTimer();\n this.clearReconnectTimer();\n if (this.socket) {\n this.socket.close();\n this.socket = null;\n }\n this.reconnectAttempts = 0;\n }\n startHeartbeat() {\n this.stopHeartbeat();\n const heartbeatInterval = this.config?.heartbeatInterval ?? 25000;\n this.heartbeatTimer = globalThis.setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return;\n }\n const heartbeatData = { type: 'PING', timestamp: Date.now() };\n this.send(heartbeatData);\n }, heartbeatInterval);\n }\n stopHeartbeat() {\n if (this.heartbeatTimer !== null) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n send(data) {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n console.warn('[SharedWorker] WebSocket 未连接,无法发送消息');\n return;\n }\n const message = typeof data === 'string' ? data : JSON.stringify(data);\n this.socket.send(message);\n }\n handleIncoming(data) {\n if (!data)\n return;\n let message;\n try {\n message = JSON.parse(data);\n }\n catch {\n console.warn('[SharedWorker] 无法解析消息', data);\n return;\n }\n if (!message?.type) {\n return;\n }\n console.log(`[SharedWorker] 📨 收到服务器消息, type: ${message.type}`, message);\n const serverMessagePayload = {\n data,\n message,\n };\n this.lastMessageByType.set(message.type, serverMessagePayload);\n let sentCount = 0;\n for (const tab of this.tabs.values()) {\n console.log(`[SharedWorker] 检查标签页 ${tab.tabId}, 注册的类型:`, Array.from(tab.registeredTypes));\n if (tab.registeredTypes.has(message.type)) {\n console.log(`[SharedWorker] ✅ 发送消息到标签页 ${tab.tabId}, type: ${message.type}`);\n this.sendToTab(tab.port, WorkerToTabMessageType.WORKER_MESSAGE, serverMessagePayload);\n sentCount++;\n }\n }\n console.log(`[SharedWorker] 消息分发完成, type: ${message.type}, 发送给 ${sentCount} 个标签页`);\n }\n registerCallback(tabId, payload) {\n const tab = this.tabs.get(tabId);\n if (!tab) {\n console.warn(`[SharedWorker] ⚠️ 标签页不存在: ${tabId}`);\n return;\n }\n tab.lastSeen = Date.now();\n tab.registeredTypes.add(payload.type);\n tab.callbackMap.set(payload.callbackId, payload.type);\n console.log(`[SharedWorker] ✅ 标签页 ${tabId} 注册回调: ${payload.type} (${payload.callbackId})`);\n console.log(`[SharedWorker] 标签页 ${tabId} 当前注册的所有类型:`, Array.from(tab.registeredTypes));\n const cached = this.lastMessageByType.get(payload.type);\n if (cached) {\n console.log(`[SharedWorker] 🔁 回放缓存消息到标签页 ${tabId}, type: ${payload.type}`);\n this.sendToTab(tab.port, WorkerToTabMessageType.WORKER_MESSAGE, cached);\n }\n }\n unregisterCallback(tabId, payload) {\n const tab = this.tabs.get(tabId);\n if (!tab) {\n console.warn(`[SharedWorker] 标签页不存在: ${tabId}`);\n return;\n }\n tab.lastSeen = Date.now();\n if (payload.callbackId) {\n const type = tab.callbackMap.get(payload.callbackId);\n if (type) {\n tab.callbackMap.delete(payload.callbackId);\n const hasOtherCallbacks = Array.from(tab.callbackMap.values()).some((t) => t === type);\n if (!hasOtherCallbacks) {\n tab.registeredTypes.delete(type);\n }\n console.log(`[SharedWorker] 标签页 ${tabId} 取消注册回调: ${type}`);\n }\n }\n else {\n tab.registeredTypes.delete(payload.type);\n for (const [callbackId, type] of tab.callbackMap.entries()) {\n if (type === payload.type) {\n tab.callbackMap.delete(callbackId);\n }\n }\n console.log(`[SharedWorker] 标签页 ${tabId} 取消注册所有 ${payload.type} 类型回调`);\n }\n }\n sendToTab(port, type, payload) {\n const message = {\n type,\n payload,\n timestamp: Date.now(),\n };\n try {\n port.postMessage(message);\n }\n catch (error) {\n console.error('[SharedWorker] 发送消息到标签页失败', error);\n }\n }\n broadcastToAllTabs(type, payload) {\n for (const tab of this.tabs.values()) {\n this.sendToTab(tab.port, type, payload);\n }\n }\n forceReset(reason) {\n console.warn('[SharedWorker] 🔄 收到强制重置指令,正在重置 Worker 状态', { reason });\n this.disconnect();\n this.lastMessageByType.clear();\n this.currentUrl = null;\n this.currentBaseUrl = null;\n this.currentUserId = null;\n this.currentToken = null;\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_DISCONNECTED, {});\n console.log('[SharedWorker] ✅ Worker 状态已重置,等待新的连接参数');\n }\n forceShutdown(reason) {\n console.warn('[SharedWorker] ⚠️ 收到强制关闭指令,正在关闭 Worker', { reason });\n this.disconnect();\n this.lastMessageByType.clear();\n this.currentUrl = null;\n this.currentBaseUrl = null;\n this.currentUserId = null;\n this.currentToken = null;\n for (const tab of this.tabs.values()) {\n try {\n tab.port.postMessage({\n type: WorkerToTabMessageType.WORKER_DISCONNECTED,\n payload: {},\n timestamp: Date.now(),\n });\n }\n catch {\n }\n try {\n tab.port.close();\n }\n catch {\n }\n }\n this.tabs.clear();\n this.stopTabCleanup();\n const workerClose = globalThis.close;\n if (typeof workerClose === 'function') {\n console.warn('[SharedWorker] 🛑 正在终止 SharedWorker 进程');\n try {\n workerClose();\n }\n catch (error) {\n console.warn('[SharedWorker] 终止 SharedWorker 失败', error);\n }\n }\n }\n handleNetworkOnline() {\n console.log('[SharedWorker] 🌐 收到网络恢复通知');\n if (this.socket && this.socket.readyState === WebSocket.OPEN) {\n console.log('[SharedWorker] 已有活跃连接,无需重连');\n return;\n }\n this.reconnectAttempts = 0;\n this.clearReconnectTimer();\n this.reconnectSuppressedUntil = 0;\n if (this.currentUrl && this.hasVisibleTab()) {\n console.log('[SharedWorker] 网络恢复,立即尝试重连');\n this.connect();\n }\n else {\n console.log('[SharedWorker] 网络恢复,但无可见标签页或无 URL,等待条件满足');\n }\n }\n}\nconst wsManager = new WebSocketManager();\nglobalThis.onconnect = (event) => {\n const port = event.ports[0];\n port.onmessage = (e) => {\n const message = e.data;\n console.log(`[SharedWorker] 📬 收到标签页消息, type: ${message.type}, tabId: ${message.tabId}`);\n const tab = wsManager.tabs?.get?.(message.tabId);\n if (tab)\n tab.lastSeen = Date.now();\n switch (message.type) {\n case 'TAB_INIT':\n wsManager.addTab(port, message);\n break;\n case 'TAB_SEND':\n wsManager.send(message.payload.data);\n break;\n case 'TAB_VISIBILITY':\n wsManager.updateTabVisibility(message.tabId, message.payload.isVisible);\n break;\n case 'TAB_REGISTER_CALLBACK':\n console.log(`[SharedWorker] 🔔 处理注册回调请求:`, message.payload);\n wsManager.registerCallback(message.tabId, message.payload);\n break;\n case 'TAB_UNREGISTER_CALLBACK':\n wsManager.unregisterCallback(message.tabId, message.payload);\n break;\n case 'TAB_DISCONNECT':\n wsManager.removeTab(message.tabId);\n break;\n case 'TAB_PING':\n port.postMessage({\n type: 'WORKER_PONG',\n timestamp: Date.now(),\n });\n break;\n case 'TAB_FORCE_RESET':\n wsManager.forceReset(message.payload?.reason);\n break;\n case 'TAB_FORCE_SHUTDOWN':\n wsManager.forceShutdown(message.payload?.reason);\n break;\n case 'TAB_NETWORK_ONLINE':\n wsManager.handleNetworkOnline();\n break;\n default:\n console.warn('[SharedWorker] 未知消息类型', message.type);\n }\n };\n port.start();\n};\n";return this.logger.debug("[SharedWorkerManager] Worker 脚本长度: 21587 字符"),e}}r.WORKER_NAME="dj-common-websocket-worker";class s{static updateLoggerLevel(){s.logger.setLevel(s.config.logLevel??s.DEFAULT_CONFIG.logLevel)}static initVisibilityListener(){"undefined"==typeof document||s.visibilityListenerInitialized||(document.addEventListener("visibilitychange",s.handleVisibilityChange),s.visibilityListenerInitialized=!0,s.logger.debug("[MessageSocket] 已初始化页面可见性监听"))}static removeVisibilityListener(){"undefined"!=typeof document&&s.visibilityListenerInitialized&&(document.removeEventListener("visibilitychange",s.handleVisibilityChange),s.visibilityListenerInitialized=!1,s.logger.debug("[MessageSocket] 已移除页面可见性监听"))}static detectSharedWorkerSupport(){return"undefined"!=typeof SharedWorker&&"undefined"!=typeof Blob}static detectVisibilitySupport(){return"undefined"!=typeof document&&void 0!==document.hidden}static determineConnectionMode(){const e=s.config.connectionMode||"auto";return"auto"===e?this.detectSharedWorkerSupport()?(s.logger.info("[MessageSocket] 自动选择 SharedWorker 模式"),"sharedWorker"):this.detectVisibilitySupport()&&s.config.enableVisibilityManagement?(s.logger.info("[MessageSocket] 自动选择 Visibility 模式"),"visibility"):(s.logger.info("[MessageSocket] 自动选择 Normal 模式"),"normal"):"sharedWorker"===e?this.detectSharedWorkerSupport()?(s.logger.info("[MessageSocket] 使用 SharedWorker 模式"),"sharedWorker"):(s.logger.warn("[MessageSocket] 浏览器不支持 SharedWorker,降级到 Visibility 模式"),this.detectVisibilitySupport()&&s.config.enableVisibilityManagement?"visibility":(s.logger.warn("[MessageSocket] 降级到 Normal 模式"),"normal")):"visibility"===e?this.detectVisibilitySupport()?(s.logger.info("[MessageSocket] 使用 Visibility 模式"),"visibility"):(s.logger.warn("[MessageSocket] 浏览器不支持 Visibility API,降级到 Normal 模式"),"normal"):(s.logger.info("[MessageSocket] 使用 Normal 模式"),"normal")}static configure(e){s.config={...s.config,...e}}static setConfig(e){return s.config={...s.config,...e},s.updateLoggerLevel(),s.config.callbacks&&s.config.callbacks.length>0&&s.setCallbacks(s.config.callbacks),s}static setCallbacks(e){return e&&0!==e.length?(s.config.callbacks=e,s):(this.logger.warn("[MessageSocket] 回调列表为空,无法设置回调"),s)}static start(e){if(!s.config.url)return void this.logger.error("[MessageSocket] 缺少配置 url, 请先调用 setConfig 设置配置!");const{userId:t,token:n}=e;t&&n?(this.logger.info("[MessageSocket] 开始连接",t),s.currentMode=s.determineConnectionMode(),"sharedWorker"===s.currentMode?s.startWithSharedWorker(e):"visibility"===s.currentMode?s.startWithVisibility(e):s.startWithNormalMode(e)):this.logger.error("[MessageSocket] 缺少 userId 或 token,无法启动")}static async startWithSharedWorker(e){const{userId:t,token:n}=e;s.stop(),s.currentUserId=t,s.currentToken=n,s.workerManager=new r({url:s.config.url,userId:t,token:n,isVisible:"undefined"==typeof document||!document.hidden,config:s.config,sharedWorkerIdleTimeout:s.config.sharedWorkerIdleTimeout,logLevel:s.config.logLevel,forceNewWorkerOnStart:s.config.forceNewWorkerOnStart}),s.workerManager.onError(t=>{s.logger.warn("[MessageSocket] SharedWorker 失败,降级到 Visibility 模式",t),s.currentMode="visibility",s.workerManager=null,s.startWithVisibility(e)});if(!await s.workerManager.start())return s.logger.warn("[MessageSocket] SharedWorker 启动失败,降级到 Visibility 模式"),s.currentMode="visibility",s.workerManager=null,void s.startWithVisibility(e);s.config.callbacks&&s.config.callbacks.length>0&&s.config.callbacks.forEach(e=>{s.workerManager.registerCallback(e)})}static startWithVisibility(e){const{userId:t,token:r}=e;if(s.client&&s.client.isConnected()&&s.currentUserId===t&&s.currentToken===r)return void this.logger.debug("[MessageSocket] 复用现有连接");s.stop(),s.currentUserId=t,s.currentToken=r;const{url:o,...a}=s.config,i=`${o}/${t}?token=${encodeURIComponent(r)}`;s.client=new n({...a,url:i}),s.config.callbacks&&s.config.callbacks.length>0&&s.config.callbacks.forEach(e=>s.client.on(e)),s.initVisibilityListener(),s.client.connect()}static startWithNormalMode(e){const{userId:t,token:r}=e;if(s.client&&s.client.isConnected()&&s.currentUserId===t&&s.currentToken===r)return void this.logger.debug("[MessageSocket] 复用现有连接");s.stop(),s.currentUserId=t,s.currentToken=r;const{url:o,...a}=s.config,i=`${o}/${t}?token=${encodeURIComponent(r)}`;s.client=new n({...a,url:i}),s.config.callbacks&&s.config.callbacks.length>0&&s.config.callbacks.forEach(e=>s.client.on(e)),s.client.connect()}static stop(){s.logger.info("[MessageSocket] 停止连接"),s.client&&(s.client.disconnect(),s.client.clearCallbacks(),s.client=null),s.workerManager&&(s.workerManager.stop(),s.workerManager=null),s.removeVisibilityListener(),s.currentUserId=null,s.currentToken=null}static registerCallbacks(e){e?"object"==typeof e?"function"==typeof e.callback?"string"==typeof e.type?"sharedWorker"===s.currentMode&&s.workerManager?s.workerManager.registerCallback(e):s.client?s.client.on(e):this.logger.warn("[MessageSocket] 无可用连接,无法注册回调"):this.logger.warn("[MessageSocket] 注册回调失败,type 不是字符串",e):this.logger.warn("[MessageSocket] 注册回调失败,callback 不是函数",e):this.logger.warn("[MessageSocket] 注册回调失败,entry 不是对象",e):this.logger.warn("[MessageSocket] 注册回调失败,缺少 entry",e)}static unregisterCallbacks(e,t){"sharedWorker"===s.currentMode&&s.workerManager?s.workerManager.unregisterCallback(e,t):s.client&&s.client.off(e,t)}static send(e){"sharedWorker"===s.currentMode&&s.workerManager?s.workerManager.send(e):s.client?s.client.send(e):this.logger.warn("[MessageSocket] 无可用连接,无法发送消息")}static getReadyState(){return s.client?.getReadyState()??WebSocket.CLOSED}static isConnected(){return"sharedWorker"===s.currentMode&&s.workerManager?s.workerManager.isConnected():s.client?.isConnected()??!1}static getConnectionMode(){return s.currentMode}static getCurrentUserId(){return s.currentUserId}static getCurrentToken(){return s.currentToken}}s.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,callbacks:[],heartbeatMessage:()=>({type:"PING",timestamp:Date.now()}),logLevel:"warn",enableVisibilityManagement:!1,connectionMode:"auto",sharedWorkerIdleTimeout:3e4,forceNewWorkerOnStart:!1,enableNetworkListener:!0},s.client=null,s.workerManager=null,s.currentMode="normal",s.logger=new t("MessageSocket",s.DEFAULT_CONFIG.logLevel),s.currentUserId=null,s.currentToken=null,s.config={...s.DEFAULT_CONFIG},s.visibilityListenerInitialized=!1,s.handleVisibilityChange=()=>{if("undefined"==typeof document)return;!document.hidden?s.currentUserId&&s.currentToken&&(s.logger.info("[MessageSocket1111111111111111111111] 页面可见,尝试重新连接"),s.client&&s.client.isConnected()||s.start({userId:s.currentUserId,token:s.currentToken})):(s.logger.info("[MessageSocket1111111111111111111111] 页面不可见,断开连接"),s.client&&s.client.disconnect())},exports.MessageSocket=s;
1
+ "use strict";const e={debug:10,info:20,warn:30,error:40,silent:50};class t{constructor(e,t="warn"){this.name=e,this.level=t}setLevel(e){this.level=e}getLevel(){return this.level}debug(...e){this.logAtLevel("debug",console.debug,e)}info(...e){this.logAtLevel("info",console.info,e)}warn(...e){this.logAtLevel("warn",console.warn,e)}error(...e){this.logAtLevel("error",console.error,e)}shouldLog(t){return"silent"!==t&&e[t]>=e[this.level]}logAtLevel(e,t,n){this.shouldLog(e)&&t(`[${this.name}]`,...n)}}class n{constructor(e={}){this.socket=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.callbackList=[],this.currentUrl=null,this.reconnectAttempts=0,this.manualClose=!1,this.networkListenerInitialized=!1,this.handleOnline=()=>{this.logger.info("[WebSocketClient] 网络已恢复"),this.manualClose||this.isConnected()||(this.reconnectAttempts=0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.currentUrl&&this.config.autoReconnect&&(this.logger.info("[WebSocketClient] 网络恢复,立即尝试重连"),this.connect(this.currentUrl)))},this.handleOffline=()=>{this.logger.info("[WebSocketClient] 网络已断开"),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null)},this.config={...n.DEFAULT_CONFIG,...e},this.logger=new t("WebSocketClient",this.config.logLevel)}updateConfig(e){this.config={...this.config,...e},this.logger.setLevel(this.config.logLevel??n.DEFAULT_CONFIG.logLevel)}connect(e){const t=e||this.config.url;if(t){this.currentUrl=t,this.manualClose=!1,this.initNetworkListener();try{this.socket=new WebSocket(t),this.socket.onopen=()=>{this.logger.info("[WebSocketClient] 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),this.onOpen()},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{this.logger.info("[WebSocketClient] 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.onClose(e),!this.manualClose&&this.config.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{this.logger.error("[WebSocketClient] 连接错误",e),this.stopHeartbeat(),this.onError(e)}}catch(e){this.logger.error("[WebSocketClient] 连接失败",e),this.config.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else this.logger.error("[WebSocketClient] 缺少 WebSocket URL")}scheduleReconnect(){if(this.reconnectAttempts>=this.config.maxReconnectAttempts||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=this.config.maxReconnectAttempts&&this.logger.warn("[WebSocketClient] 已达到最大重连次数"));this.reconnectAttempts+=1;const e=Math.min(this.config.reconnectDelay*this.reconnectAttempts,this.config.reconnectDelayMax);this.logger.debug(`[WebSocketClient] 将在 ${e}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=window.setTimeout(()=>{this.connect(this.currentUrl)},e)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.removeNetworkListener(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.currentUrl=null,this.reconnectAttempts=0}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void this.logger.warn("[WebSocketClient] WebSocket 未连接,无法发送消息");const t="string"==typeof e?e:JSON.stringify(e);this.socket.send(t)}handleIncoming(e){if(!e)return;let t;try{t=JSON.parse(e)}catch{return void this.logger.warn("[WebSocketClient] 无法解析消息",e)}if(!t?.type)return;this.callbackList.filter(e=>e.type===t.type).forEach(({callback:e})=>{try{e(t.data,t)}catch(e){this.logger.error("[WebSocketClient] 回调执行失败",e)}}),this.onMessage(t)}startHeartbeat(){this.stopHeartbeat(),this.socket&&this.socket.readyState===WebSocket.OPEN&&(this.heartbeatTimer=window.setInterval(()=>{if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return;const e=this.config.heartbeatMessage();this.send(e)},this.config.heartbeatInterval))}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}on(e,t){const n="string"==typeof e?{type:e,callback:t}:e;n.type&&"function"==typeof n.callback?this.callbackList.push(n):this.logger.warn("[WebSocketClient] 无效的回调配置",n)}off(e,t){this.callbackList=t?this.callbackList.filter(n=>!(n.type===e&&n.callback===t)):this.callbackList.filter(t=>t.type!==e)}clearCallbacks(){this.callbackList=[]}getReadyState(){return this.socket?.readyState??WebSocket.CLOSED}isConnected(){return this.socket?.readyState===WebSocket.OPEN}onOpen(){this.logger.info("[WebSocketClient] 连接打开")}onClose(e){this.logger.info("[WebSocketClient] 连接打开")}onError(e){this.logger.error("[WebSocketClient] 连接错误",e)}onMessage(e){this.logger.debug("[WebSocketClient] 收到消息",e)}initNetworkListener(){"undefined"==typeof window||this.networkListenerInitialized||this.config.enableNetworkListener&&(window.addEventListener("online",this.handleOnline),window.addEventListener("offline",this.handleOffline),this.networkListenerInitialized=!0,this.logger.debug("[WebSocketClient] 已初始化网络状态监听"))}removeNetworkListener(){"undefined"!=typeof window&&this.networkListenerInitialized&&(window.removeEventListener("online",this.handleOnline),window.removeEventListener("offline",this.handleOffline),this.networkListenerInitialized=!1,this.logger.debug("[WebSocketClient] 已移除网络状态监听"))}}n.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,heartbeatMessage:()=>({type:"PING",timestamp:Date.now()}),logLevel:"warn",enableNetworkListener:!0};class r{constructor(e){this.worker=null,this.port=null,this.callbacks=new Map,this.callbackIdCounter=0,this.connected=!1,this.visibilityListenerInitialized=!1,this.onConnectedCallback=null,this.onDisconnectedCallback=null,this.onErrorCallback=null,this.onAuthConflictCallback=null,this.pingTimer=null,this.unloadListenerInitialized=!1,this.networkListenerInitialized=!1,this.handleVisibilityChange=()=>{const e=!document.hidden;if(this.logger.debug(`[SharedWorkerManager] 页面可见性变化: ${e}`),this.port)if(e&&!this.connected)this.logger.info("[SharedWorkerManager] 页面变为可见且未连接,重新初始化连接"),this.reinitialize();else{const t={isVisible:e};this.sendToWorker("TAB_VISIBILITY",t)}},this.handlePageHide=()=>{this.port&&this.sendToWorker("TAB_DISCONNECT",{})},this.handleOnline=()=>{this.logger.info("[SharedWorkerManager] 网络已恢复,通知 Worker 重连"),this.port&&this.sendToWorker("TAB_NETWORK_ONLINE",{})},this.handleOffline=()=>{this.logger.info("[SharedWorkerManager] 网络已断开")},this.config=e,this.tabId=this.generateTabId(),this.logger=new t("SharedWorkerManager",e.logLevel??"warn")}generateTabId(){return`tab_${Date.now()}_${Math.random().toString(36).substring(2,9)}`}async sendResetToExistingWorker(e){try{const t=new SharedWorker(e,{name:r.WORKER_NAME}).port;t.start(),t.postMessage({type:"TAB_FORCE_RESET",payload:{reason:"force_new_start"},tabId:this.tabId,timestamp:Date.now()}),await new Promise(e=>setTimeout(e,100)),t.close(),this.logger.debug("[SharedWorkerManager] 已发送重置命令到现有 Worker")}catch(e){this.logger.warn("[SharedWorkerManager] 发送重置命令失败(可忽略)",e)}}async start(){try{this.logger.debug("[SharedWorkerManager] 开始启动 SharedWorker");const e=this.getWorkerScriptDataUrl();this.logger.debug(`[SharedWorkerManager] Worker Script URL 创建成功: ${e.slice(0,60)}...`),this.config.forceNewWorkerOnStart&&(this.logger.debug("[SharedWorkerManager] forceNewWorkerOnStart=true,发送重置命令"),await this.sendResetToExistingWorker(e)),this.logger.debug("[SharedWorkerManager] 正在创建 SharedWorker 实例..."),this.worker=new SharedWorker(e,{name:r.WORKER_NAME}),this.logger.debug("[SharedWorkerManager] ✅ SharedWorker 实例创建成功"),this.port=this.worker.port,this.port.onmessage=this.handleWorkerMessage.bind(this),this.port.start(),this.logger.debug("[SharedWorkerManager] ✅ MessagePort 已启动"),this.setupVisibilityListener(),this.setupUnloadListener(),this.setupNetworkListener();const t={heartbeatInterval:this.config.config.heartbeatInterval,maxReconnectAttempts:this.config.config.maxReconnectAttempts,reconnectDelay:this.config.config.reconnectDelay,reconnectDelayMax:this.config.config.reconnectDelayMax,autoReconnect:this.config.config.autoReconnect,logLevel:this.config.config.logLevel};return this.sendToWorker("TAB_INIT",{url:this.config.url,userId:this.config.userId,token:this.config.token,isVisible:!document.hidden,config:t,sharedWorkerIdleTimeout:this.config.sharedWorkerIdleTimeout}),this.startPing(),this.logger.info("[SharedWorkerManager] SharedWorker 已启动"),!0}catch(e){return this.logger.error("[SharedWorkerManager] 启动 SharedWorker 失败",e),!1}}stop(){this.logger.debug("[SharedWorkerManager] 停止当前标签页的 SharedWorker 连接");const e=this.port;this.port&&this.sendToWorker("TAB_DISCONNECT",{}),this.stopPing(),this.removeVisibilityListener(),this.removeUnloadListener(),this.removeNetworkListener(),this.port=null,this.worker=null,this.connected=!1,this.callbacks.clear(),e&&setTimeout(()=>{try{e.close()}catch{}},100)}forceShutdown(){this.logger.debug("[SharedWorkerManager] 强制关闭 Worker(退出登录)");const e=this.port;this.port&&this.sendToWorker("TAB_FORCE_SHUTDOWN",{reason:"logout"}),this.stopPing(),this.removeVisibilityListener(),this.removeUnloadListener(),this.removeNetworkListener(),this.port=null,this.worker=null,this.connected=!1,this.callbacks.clear(),e&&setTimeout(()=>{try{e.close()}catch{}},100)}send(e){if(!this.port)return void this.logger.warn("[SharedWorkerManager] MessagePort 未初始化,无法发送消息");const t={data:e};this.sendToWorker("TAB_SEND",t)}registerCallback(e){const t="callback_"+this.callbackIdCounter++,n={...e,id:t};if(this.callbacks.set(t,n),this.port){const n={type:e.type,callbackId:t};this.sendToWorker("TAB_REGISTER_CALLBACK",n),this.logger.debug(`[SharedWorkerManager] ✅ 已发送注册消息到 Worker: ${e.type} (${t})`)}else this.logger.warn(`[SharedWorkerManager] ⚠️ port 未初始化,无法发送注册消息: ${e.type}`);return this.logger.debug(`[SharedWorkerManager] 注册回调: ${e.type} (${t}), 当前回调总数: ${this.callbacks.size}`),t}unregisterCallback(e,t){if(t){let n=null;for(const[r,s]of this.callbacks.entries())if(s.type===e&&s.callback===t){n=r,this.callbacks.delete(r);break}if(n&&this.port){const t={type:e,callbackId:n};this.sendToWorker("TAB_UNREGISTER_CALLBACK",t)}this.logger.debug(`[SharedWorkerManager] 取消注册回调: ${e} (${n})`)}else{const t=[];for(const[n,r]of this.callbacks.entries())r.type===e&&(t.push(n),this.callbacks.delete(n));if(this.port){const t={type:e};this.sendToWorker("TAB_UNREGISTER_CALLBACK",t)}this.logger.debug(`[SharedWorkerManager] 取消注册所有 ${e} 类型回调 (${t.length} 个)`)}}clearCallbacks(){for(const e of this.callbacks.values())if(this.port){const t={type:e.type,callbackId:e.id};this.sendToWorker("TAB_UNREGISTER_CALLBACK",t)}this.callbacks.clear(),this.logger.debug("[SharedWorkerManager] 已清空所有回调")}isConnected(){return this.connected}onConnected(e){this.onConnectedCallback=e}onDisconnected(e){this.onDisconnectedCallback=e}onError(e){this.onErrorCallback=e}onAuthConflict(e){this.onAuthConflictCallback=e}handleWorkerMessage(e){const t=e.data;switch(this.logger.debug(`[SharedWorkerManager] 📬 收到 Worker 消息, type: ${t.type}`),t.type){case"WORKER_CONNECTED":this.connected=!0,this.logger.info("[SharedWorkerManager] ✅ WebSocket 已连接"),this.onConnectedCallback?.();break;case"WORKER_DISCONNECTED":this.connected=!1,this.logger.info("[SharedWorkerManager] WebSocket 已断开"),this.onDisconnectedCallback?.();break;case"WORKER_MESSAGE":try{const e=t.payload;console.log("[SharedWorkerManager] 📨 收到服务器消息(经 Worker 转发):",e?.message),this.logger.info("[SharedWorkerManager] 📨 收到服务器消息(经 Worker 转发)",e?.message),this.logger.debug("[SharedWorkerManager] 🧾 原始消息 data:",e?.data)}catch(e){this.logger.warn("[SharedWorkerManager] 打印服务器消息失败",e)}this.handleServerMessage(t.payload);break;case"WORKER_ERROR":this.logger.error("[SharedWorkerManager] Worker 错误",t.payload),this.onErrorCallback?.(t.payload);break;case"WORKER_AUTH_CONFLICT":this.logger.warn("[SharedWorkerManager] 身份冲突",t.payload),this.onAuthConflictCallback?.(t.payload);break;case"WORKER_PONG":this.logger.debug("[SharedWorkerManager] 收到 PONG");break;case"WORKER_TAB_NOT_FOUND":this.logger.warn("[SharedWorkerManager] Worker 通知标签页不存在,需要重新初始化"),this.reinitialize();break;default:this.logger.warn("[SharedWorkerManager] 未知消息类型",t.type)}}handleServerMessage(e){const{message:t}=e;this.logger.debug(`[SharedWorkerManager] 📨 收到服务器消息, type: ${t.type}`),this.logger.debug("[SharedWorkerManager] 当前注册的回调类型:",Array.from(this.callbacks.values()).map(e=>e.type));let n=0;for(const e of this.callbacks.values())if(e.type===t.type){n++,this.logger.debug(`[SharedWorkerManager] ✅ 匹配到回调 ${e.type} (${e.id}),准备执行`);try{e.callback(t.data,t),this.logger.debug(`[SharedWorkerManager] ✅ 回调执行成功 ${e.type} (${e.id})`)}catch(e){this.logger.error("[SharedWorkerManager] ❌ 回调执行失败",e)}}0===n&&this.logger.warn(`[SharedWorkerManager] ⚠️ 没有匹配的回调: ${t.type}`)}sendToWorker(e,t){if(!this.port)return void this.logger.warn("[SharedWorkerManager] MessagePort 未初始化,无法发送消息");const n={type:e,payload:t,tabId:this.tabId,timestamp:Date.now()};try{this.port.postMessage(n)}catch(e){this.logger.error("[SharedWorkerManager] 发送消息到 Worker 失败",e)}}setupVisibilityListener(){"undefined"==typeof document||this.visibilityListenerInitialized||(document.addEventListener("visibilitychange",this.handleVisibilityChange),this.visibilityListenerInitialized=!0,this.logger.debug("[SharedWorkerManager] 已设置页面可见性监听"))}removeVisibilityListener(){"undefined"!=typeof document&&this.visibilityListenerInitialized&&(document.removeEventListener("visibilitychange",this.handleVisibilityChange),this.visibilityListenerInitialized=!1,this.logger.debug("[SharedWorkerManager] 已移除页面可见性监听"))}reinitialize(){if(!this.port)return void this.logger.warn("[SharedWorkerManager] 无法重新初始化:port 未初始化");const e={heartbeatInterval:this.config.config.heartbeatInterval,maxReconnectAttempts:this.config.config.maxReconnectAttempts,reconnectDelay:this.config.config.reconnectDelay,reconnectDelayMax:this.config.config.reconnectDelayMax,autoReconnect:this.config.config.autoReconnect,logLevel:this.config.config.logLevel};this.sendToWorker("TAB_INIT",{url:this.config.url,userId:this.config.userId,token:this.config.token,isVisible:!0,config:e,sharedWorkerIdleTimeout:this.config.sharedWorkerIdleTimeout});for(const e of this.callbacks.values()){const t={type:e.type,callbackId:e.id};this.sendToWorker("TAB_REGISTER_CALLBACK",t),this.logger.debug(`[SharedWorkerManager] 重新注册回调: ${e.type} (${e.id})`)}this.logger.info("[SharedWorkerManager] 已发送重新初始化消息")}startPing(){this.stopPing(),"undefined"!=typeof window&&this.port&&(this.pingTimer=globalThis.setInterval(()=>{this.port&&this.sendToWorker("TAB_PING",{})},1e4))}stopPing(){null!==this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null)}setupUnloadListener(){"undefined"==typeof window||this.unloadListenerInitialized||(window.addEventListener("pagehide",this.handlePageHide,{capture:!0}),window.addEventListener("beforeunload",this.handlePageHide,{capture:!0}),this.unloadListenerInitialized=!0,this.logger.debug("[SharedWorkerManager] 已设置页面卸载监听"))}removeUnloadListener(){"undefined"!=typeof window&&this.unloadListenerInitialized&&(window.removeEventListener("pagehide",this.handlePageHide,{capture:!0}),window.removeEventListener("beforeunload",this.handlePageHide,{capture:!0}),this.unloadListenerInitialized=!1,this.logger.debug("[SharedWorkerManager] 已移除页面卸载监听"))}setupNetworkListener(){"undefined"==typeof window||this.networkListenerInitialized||(window.addEventListener("online",this.handleOnline),window.addEventListener("offline",this.handleOffline),this.networkListenerInitialized=!0,this.logger.debug("[SharedWorkerManager] 已设置网络状态监听"))}removeNetworkListener(){"undefined"!=typeof window&&this.networkListenerInitialized&&(window.removeEventListener("online",this.handleOnline),window.removeEventListener("offline",this.handleOffline),this.networkListenerInitialized=!1,this.logger.debug("[SharedWorkerManager] 已移除网络状态监听"))}getWorkerScriptDataUrl(){const e=this.getWorkerScriptContent();this.logger.debug(`[SharedWorkerManager] 正在创建 Worker Data URL, 代码长度: ${e.length}`);return`data:application/javascript;charset=utf-8;base64,${this.toBase64Utf8(e)}`}toBase64Utf8(e){try{if("undefined"!=typeof TextEncoder){const t=(new TextEncoder).encode(e);let n="";for(let e=0;e<t.length;e++)n+=String.fromCharCode(t[e]);return btoa(n)}return btoa(unescape(encodeURIComponent(e)))}catch(e){throw this.logger.error("[SharedWorkerManager] ❌ Worker 脚本 base64 编码失败",e),e}}getWorkerScriptContent(){const e="const WorkerToTabMessageType = {\n WORKER_READY: 'WORKER_READY',\n WORKER_MESSAGE: 'WORKER_MESSAGE',\n WORKER_CONNECTED: 'WORKER_CONNECTED',\n WORKER_DISCONNECTED: 'WORKER_DISCONNECTED',\n WORKER_ERROR: 'WORKER_ERROR',\n WORKER_AUTH_CONFLICT: 'WORKER_AUTH_CONFLICT',\n WORKER_PONG: 'WORKER_PONG',\n WORKER_TAB_NOT_FOUND: 'WORKER_TAB_NOT_FOUND',\n};\nclass WebSocketManager {\n constructor() {\n this.tabs = new Map();\n this.socket = null;\n this.lastMessageByType = new Map();\n this.tabCleanupTimer = null;\n this.idleTimer = null;\n this.heartbeatTimer = null;\n this.reconnectTimer = null;\n this.reconnectAttempts = 0;\n this.manualClose = false;\n this.currentUrl = null;\n this.currentUserId = null;\n this.currentToken = null;\n this.currentBaseUrl = null;\n this.lastOpenAt = 0;\n this.fastClose1000Count = 0;\n this.reconnectSuppressedUntil = 0;\n this.config = null;\n this.sharedWorkerIdleTimeout = 30000;\n this.tabStaleTimeout = 45000;\n }\n hasVisibleTab() {\n return Array.from(this.tabs.values()).some((tab) => tab.isVisible);\n }\n clearReconnectTimer() {\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n startTabCleanup() {\n if (this.tabCleanupTimer !== null)\n return;\n this.tabCleanupTimer = globalThis.setInterval(() => {\n const now = Date.now();\n const staleTabIds = [];\n for (const tab of this.tabs.values()) {\n if (now - tab.lastSeen > this.tabStaleTimeout) {\n staleTabIds.push(tab.tabId);\n }\n }\n if (staleTabIds.length > 0) {\n console.warn('[SharedWorker] 检测到过期标签页,将清理:', staleTabIds);\n staleTabIds.forEach((id) => this.removeTab(id));\n }\n }, 15000);\n }\n stopTabCleanup() {\n if (this.tabCleanupTimer !== null) {\n clearInterval(this.tabCleanupTimer);\n this.tabCleanupTimer = null;\n }\n }\n addTab(port, message) {\n const { tabId, payload } = message;\n const initPayload = payload;\n if (this.currentUserId && this.currentUserId !== initPayload.userId) {\n const conflictPayload = {\n currentUserId: this.currentUserId,\n newUserId: initPayload.userId,\n message: `检测到不同用户身份:当前连接用户为 ${this.currentUserId},新标签页尝试使用用户 ${initPayload.userId} 连接。将复用现有连接。`,\n };\n this.sendToTab(port, WorkerToTabMessageType.WORKER_AUTH_CONFLICT, conflictPayload);\n console.warn('[SharedWorker]', conflictPayload.message);\n }\n const tabInfo = {\n port,\n tabId,\n isVisible: initPayload.isVisible,\n lastSeen: Date.now(),\n registeredTypes: new Set(),\n callbackMap: new Map(),\n };\n this.tabs.set(tabId, tabInfo);\n console.log(`[SharedWorker] 标签页已添加: ${tabId}, 当前标签页数量: ${this.tabs.size}`);\n this.startTabCleanup();\n const nextBaseUrl = initPayload.url;\n const nextUserId = initPayload.userId;\n const nextToken = initPayload.token;\n const nextUrl = `${nextBaseUrl}/${nextUserId}?token=${encodeURIComponent(nextToken)}`;\n const hasExistingIdentity = this.currentBaseUrl !== null ||\n this.currentUserId !== null ||\n this.currentToken !== null ||\n this.currentUrl !== null;\n const identityChanged = (this.currentBaseUrl !== null && this.currentBaseUrl !== nextBaseUrl) ||\n (this.currentUserId !== null && this.currentUserId !== nextUserId) ||\n (this.currentToken !== null && this.currentToken !== nextToken) ||\n (this.currentUrl !== null && this.currentUrl !== nextUrl);\n if (!hasExistingIdentity || identityChanged) {\n if (hasExistingIdentity) {\n const conflictPayload = {\n currentUserId: this.currentUserId ?? '',\n newUserId: nextUserId,\n message: `检测到连接身份/参数变化:` +\n `oldUser=${this.currentUserId ?? 'null'} -> newUser=${nextUserId}, ` +\n `oldBaseUrl=${this.currentBaseUrl ?? 'null'} -> newBaseUrl=${nextBaseUrl}, ` +\n `tokenChanged=${this.currentToken ? this.currentToken !== nextToken : true}。` +\n `将切换到最新登录态并重建连接。`,\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_AUTH_CONFLICT, conflictPayload);\n console.warn('[SharedWorker]', conflictPayload.message);\n }\n this.currentBaseUrl = nextBaseUrl;\n this.currentUserId = nextUserId;\n this.currentToken = nextToken;\n this.currentUrl = nextUrl;\n this.config = initPayload.config;\n this.sharedWorkerIdleTimeout = initPayload.sharedWorkerIdleTimeout ?? 30000;\n this.lastMessageByType.clear();\n if (this.socket) {\n console.log('[SharedWorker] 检测到登录态变化,断开旧连接以使用新参数');\n this.disconnect();\n }\n }\n else {\n this.config = initPayload.config;\n this.sharedWorkerIdleTimeout = initPayload.sharedWorkerIdleTimeout ?? this.sharedWorkerIdleTimeout;\n }\n if (this.socket && this.socket.readyState === WebSocket.OPEN) {\n this.sendToTab(port, WorkerToTabMessageType.WORKER_CONNECTED, {});\n }\n this.checkAllTabsVisibility();\n }\n removeTab(tabId) {\n this.tabs.delete(tabId);\n console.log(`[SharedWorker] 标签页已移除: ${tabId}, 剩余标签页数量: ${this.tabs.size}`);\n if (this.tabs.size === 0) {\n console.log(`[SharedWorker] 所有标签页已关闭,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`);\n this.clearReconnectTimer();\n this.stopTabCleanup();\n this.startIdleTimer();\n }\n else {\n this.checkAllTabsVisibility();\n }\n }\n updateTabVisibility(tabId, isVisible) {\n const tab = this.tabs.get(tabId);\n if (!tab) {\n console.warn(`[SharedWorker] 标签页不存在: ${tabId}`);\n return false;\n }\n tab.isVisible = isVisible;\n tab.lastSeen = Date.now();\n console.log(`[SharedWorker] 标签页 ${tabId} 可见性更新: ${isVisible}`);\n this.checkAllTabsVisibility();\n return true;\n }\n checkAllTabsVisibility() {\n if (this.tabs.size === 0)\n return;\n const allHidden = Array.from(this.tabs.values()).every((tab) => !tab.isVisible);\n if (allHidden) {\n console.log(`[SharedWorker] 所有标签页都不可见,将在 ${this.sharedWorkerIdleTimeout}ms 后断开连接`);\n this.clearReconnectTimer();\n this.startIdleTimer();\n }\n else {\n console.log('[SharedWorker] 至少有一个标签页可见,保持连接');\n this.resetIdleTimer();\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n if (this.currentUrl) {\n console.log('[SharedWorker] 检测到连接已断开,标签页可见,尝试重新连接');\n this.connect();\n }\n }\n }\n }\n startIdleTimer() {\n this.clearIdleTimer();\n this.idleTimer = globalThis.setTimeout(() => {\n console.log('[SharedWorker] 空闲超时,断开连接');\n this.disconnect();\n }, this.sharedWorkerIdleTimeout);\n }\n resetIdleTimer() {\n this.clearIdleTimer();\n }\n clearIdleTimer() {\n if (this.idleTimer !== null) {\n clearTimeout(this.idleTimer);\n this.idleTimer = null;\n }\n }\n connect() {\n if (!this.currentUrl) {\n console.error('[SharedWorker] 缺少 WebSocket URL');\n return;\n }\n if (Date.now() < this.reconnectSuppressedUntil) {\n console.warn('[SharedWorker] 自动重连已熔断,暂不连接', {\n suppressedUntil: this.reconnectSuppressedUntil,\n });\n return;\n }\n if (!this.hasVisibleTab()) {\n console.log('[SharedWorker] 当前无可见标签页,跳过连接');\n return;\n }\n if (this.socket && (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING)) {\n return;\n }\n this.manualClose = false;\n this.clearReconnectTimer();\n try {\n this.socket = new WebSocket(this.currentUrl);\n this.socket.onopen = () => {\n console.log('[SharedWorker] ✅ WebSocket 连接成功');\n this.reconnectAttempts = 0;\n this.lastOpenAt = Date.now();\n this.fastClose1000Count = 0;\n this.startHeartbeat();\n console.log(`[SharedWorker] 通知 ${this.tabs.size} 个标签页: 已连接`);\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_CONNECTED, {});\n };\n this.socket.onmessage = (event) => {\n this.handleIncoming(event.data);\n };\n this.socket.onclose = (event) => {\n const now = Date.now();\n const liveMs = this.lastOpenAt ? now - this.lastOpenAt : -1;\n console.log('[SharedWorker] WebSocket 连接关闭', {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean,\n liveMs,\n });\n this.stopHeartbeat();\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_DISCONNECTED, {});\n if (event.code === 1000 && liveMs >= 0 && liveMs < 3000) {\n this.fastClose1000Count += 1;\n console.warn('[SharedWorker] 检测到快速 1000 关闭', {\n fastClose1000Count: this.fastClose1000Count,\n liveMs,\n });\n if (this.fastClose1000Count >= 3) {\n this.reconnectSuppressedUntil = now + 60000;\n const errorPayload = {\n message: 'WebSocket 被服务端频繁正常关闭(1000),已临时暂停自动重连 60s。请检查 token/服务端是否限制同账号多连接/是否需要额外鉴权消息。',\n error: {\n code: event.code,\n reason: event.reason,\n liveMs,\n },\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload);\n return;\n }\n }\n else {\n this.fastClose1000Count = 0;\n }\n if (!this.manualClose && this.config?.autoReconnect && this.tabs.size > 0 && this.hasVisibleTab()) {\n this.scheduleReconnect();\n }\n };\n this.socket.onerror = (event) => {\n console.error('[SharedWorker] WebSocket 连接错误', event);\n this.stopHeartbeat();\n const errorPayload = {\n message: 'WebSocket 连接错误',\n error: event,\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload);\n };\n }\n catch (error) {\n console.error('[SharedWorker] 创建 WebSocket 连接失败', error);\n const errorPayload = {\n message: '创建 WebSocket 连接失败',\n error,\n };\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_ERROR, errorPayload);\n if (this.config?.autoReconnect && !this.manualClose) {\n this.scheduleReconnect();\n }\n }\n }\n scheduleReconnect() {\n if (this.tabs.size === 0 || !this.hasVisibleTab()) {\n return;\n }\n const maxAttempts = this.config?.maxReconnectAttempts ?? 10;\n const reconnectDelay = this.config?.reconnectDelay ?? 3000;\n const reconnectDelayMax = this.config?.reconnectDelayMax ?? 10000;\n if (this.reconnectAttempts >= maxAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= maxAttempts) {\n console.warn('[SharedWorker] 已达到最大重连次数');\n }\n return;\n }\n this.reconnectAttempts += 1;\n const delay = Math.min(reconnectDelay * this.reconnectAttempts, reconnectDelayMax);\n console.log(`[SharedWorker] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`);\n this.clearReconnectTimer();\n this.reconnectTimer = globalThis.setTimeout(() => {\n this.connect();\n }, delay);\n }\n disconnect() {\n this.manualClose = true;\n this.stopHeartbeat();\n this.clearIdleTimer();\n this.clearReconnectTimer();\n if (this.socket) {\n this.socket.close();\n this.socket = null;\n }\n this.reconnectAttempts = 0;\n }\n startHeartbeat() {\n this.stopHeartbeat();\n const heartbeatInterval = this.config?.heartbeatInterval ?? 25000;\n this.heartbeatTimer = globalThis.setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return;\n }\n const heartbeatData = { type: 'PING', timestamp: Date.now() };\n this.send(heartbeatData);\n }, heartbeatInterval);\n }\n stopHeartbeat() {\n if (this.heartbeatTimer !== null) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n send(data) {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n console.warn('[SharedWorker] WebSocket 未连接,无法发送消息');\n return;\n }\n const message = typeof data === 'string' ? data : JSON.stringify(data);\n this.socket.send(message);\n }\n handleIncoming(data) {\n if (!data)\n return;\n let message;\n try {\n message = JSON.parse(data);\n }\n catch {\n console.warn('[SharedWorker] 无法解析消息', data);\n return;\n }\n if (!message?.type) {\n return;\n }\n console.log(`[SharedWorker] 📨 收到服务器消息, type: ${message.type}`, message);\n const serverMessagePayload = {\n data,\n message,\n };\n this.lastMessageByType.set(message.type, serverMessagePayload);\n let sentCount = 0;\n for (const tab of this.tabs.values()) {\n console.log(`[SharedWorker] 检查标签页 ${tab.tabId}, 注册的类型:`, Array.from(tab.registeredTypes));\n if (tab.registeredTypes.has(message.type)) {\n console.log(`[SharedWorker] ✅ 发送消息到标签页 ${tab.tabId}, type: ${message.type}`);\n this.sendToTab(tab.port, WorkerToTabMessageType.WORKER_MESSAGE, serverMessagePayload);\n sentCount++;\n }\n }\n console.log(`[SharedWorker] 消息分发完成, type: ${message.type}, 发送给 ${sentCount} 个标签页`);\n }\n registerCallback(tabId, payload) {\n const tab = this.tabs.get(tabId);\n if (!tab) {\n console.warn(`[SharedWorker] ⚠️ 标签页不存在: ${tabId}`);\n return;\n }\n tab.lastSeen = Date.now();\n tab.registeredTypes.add(payload.type);\n tab.callbackMap.set(payload.callbackId, payload.type);\n console.log(`[SharedWorker] ✅ 标签页 ${tabId} 注册回调: ${payload.type} (${payload.callbackId})`);\n console.log(`[SharedWorker] 标签页 ${tabId} 当前注册的所有类型:`, Array.from(tab.registeredTypes));\n const cached = this.lastMessageByType.get(payload.type);\n if (cached) {\n console.log(`[SharedWorker] 🔁 回放缓存消息到标签页 ${tabId}, type: ${payload.type}`);\n this.sendToTab(tab.port, WorkerToTabMessageType.WORKER_MESSAGE, cached);\n }\n }\n unregisterCallback(tabId, payload) {\n const tab = this.tabs.get(tabId);\n if (!tab) {\n console.warn(`[SharedWorker] 标签页不存在: ${tabId}`);\n return;\n }\n tab.lastSeen = Date.now();\n if (payload.callbackId) {\n const type = tab.callbackMap.get(payload.callbackId);\n if (type) {\n tab.callbackMap.delete(payload.callbackId);\n const hasOtherCallbacks = Array.from(tab.callbackMap.values()).some((t) => t === type);\n if (!hasOtherCallbacks) {\n tab.registeredTypes.delete(type);\n }\n console.log(`[SharedWorker] 标签页 ${tabId} 取消注册回调: ${type}`);\n }\n }\n else {\n tab.registeredTypes.delete(payload.type);\n for (const [callbackId, type] of tab.callbackMap.entries()) {\n if (type === payload.type) {\n tab.callbackMap.delete(callbackId);\n }\n }\n console.log(`[SharedWorker] 标签页 ${tabId} 取消注册所有 ${payload.type} 类型回调`);\n }\n }\n sendToTab(port, type, payload) {\n const message = {\n type,\n payload,\n timestamp: Date.now(),\n };\n try {\n port.postMessage(message);\n }\n catch (error) {\n console.error('[SharedWorker] 发送消息到标签页失败', error);\n }\n }\n broadcastToAllTabs(type, payload) {\n for (const tab of this.tabs.values()) {\n this.sendToTab(tab.port, type, payload);\n }\n }\n forceReset(reason) {\n console.warn('[SharedWorker] 🔄 收到强制重置指令,正在重置 Worker 状态', { reason });\n this.disconnect();\n this.lastMessageByType.clear();\n this.currentUrl = null;\n this.currentBaseUrl = null;\n this.currentUserId = null;\n this.currentToken = null;\n this.broadcastToAllTabs(WorkerToTabMessageType.WORKER_DISCONNECTED, {});\n console.log('[SharedWorker] ✅ Worker 状态已重置,等待新的连接参数');\n }\n forceShutdown(reason) {\n console.warn('[SharedWorker] ⚠️ 收到强制关闭指令,正在关闭 Worker', { reason });\n this.disconnect();\n this.lastMessageByType.clear();\n this.currentUrl = null;\n this.currentBaseUrl = null;\n this.currentUserId = null;\n this.currentToken = null;\n for (const tab of this.tabs.values()) {\n try {\n tab.port.postMessage({\n type: WorkerToTabMessageType.WORKER_DISCONNECTED,\n payload: {},\n timestamp: Date.now(),\n });\n }\n catch {\n }\n try {\n tab.port.close();\n }\n catch {\n }\n }\n this.tabs.clear();\n this.stopTabCleanup();\n const workerClose = globalThis.close;\n if (typeof workerClose === 'function') {\n console.warn('[SharedWorker] 🛑 正在终止 SharedWorker 进程');\n try {\n workerClose();\n }\n catch (error) {\n console.warn('[SharedWorker] 终止 SharedWorker 失败', error);\n }\n }\n }\n handleNetworkOnline() {\n console.log('[SharedWorker] 🌐 收到网络恢复通知');\n if (this.socket && this.socket.readyState === WebSocket.OPEN) {\n console.log('[SharedWorker] 已有活跃连接,无需重连');\n return;\n }\n this.reconnectAttempts = 0;\n this.clearReconnectTimer();\n this.reconnectSuppressedUntil = 0;\n if (this.currentUrl && this.hasVisibleTab()) {\n console.log('[SharedWorker] 网络恢复,立即尝试重连');\n this.connect();\n }\n else {\n console.log('[SharedWorker] 网络恢复,但无可见标签页或无 URL,等待条件满足');\n }\n }\n}\nconst wsManager = new WebSocketManager();\nglobalThis.onconnect = (event) => {\n const port = event.ports[0];\n port.onmessage = (e) => {\n const message = e.data;\n console.log(`[SharedWorker] 📬 收到标签页消息, type: ${message.type}, tabId: ${message.tabId}`);\n const tab = wsManager.tabs?.get?.(message.tabId);\n if (tab)\n tab.lastSeen = Date.now();\n switch (message.type) {\n case 'TAB_INIT':\n wsManager.addTab(port, message);\n break;\n case 'TAB_SEND':\n wsManager.send(message.payload.data);\n break;\n case 'TAB_VISIBILITY': {\n const updated = wsManager.updateTabVisibility(message.tabId, message.payload.isVisible);\n if (!updated) {\n console.log(`[SharedWorker] 标签页 ${message.tabId} 不存在,发送 TAB_NOT_FOUND 通知`);\n port.postMessage({\n type: WorkerToTabMessageType.WORKER_TAB_NOT_FOUND,\n payload: { tabId: message.tabId },\n timestamp: Date.now(),\n });\n }\n break;\n }\n case 'TAB_REGISTER_CALLBACK':\n console.log(`[SharedWorker] 🔔 处理注册回调请求:`, message.payload);\n wsManager.registerCallback(message.tabId, message.payload);\n break;\n case 'TAB_UNREGISTER_CALLBACK':\n wsManager.unregisterCallback(message.tabId, message.payload);\n break;\n case 'TAB_DISCONNECT':\n wsManager.removeTab(message.tabId);\n break;\n case 'TAB_PING':\n port.postMessage({\n type: 'WORKER_PONG',\n timestamp: Date.now(),\n });\n break;\n case 'TAB_FORCE_RESET':\n wsManager.forceReset(message.payload?.reason);\n break;\n case 'TAB_FORCE_SHUTDOWN':\n wsManager.forceShutdown(message.payload?.reason);\n break;\n case 'TAB_NETWORK_ONLINE':\n wsManager.handleNetworkOnline();\n break;\n default:\n console.warn('[SharedWorker] 未知消息类型', message.type);\n }\n };\n port.start();\n};\n";return this.logger.debug("[SharedWorkerManager] Worker 脚本长度: 22086 字符"),e}}r.WORKER_NAME="dj-common-websocket-worker";class s{static updateLoggerLevel(){s.logger.setLevel(s.config.logLevel??s.DEFAULT_CONFIG.logLevel)}static initVisibilityListener(){"undefined"==typeof document||s.visibilityListenerInitialized||(document.addEventListener("visibilitychange",s.handleVisibilityChange),s.visibilityListenerInitialized=!0,s.logger.debug("[MessageSocket] 已初始化页面可见性监听"))}static removeVisibilityListener(){"undefined"!=typeof document&&s.visibilityListenerInitialized&&(document.removeEventListener("visibilitychange",s.handleVisibilityChange),s.visibilityListenerInitialized=!1,s.logger.debug("[MessageSocket] 已移除页面可见性监听"))}static detectSharedWorkerSupport(){return"undefined"!=typeof SharedWorker&&"undefined"!=typeof Blob}static detectVisibilitySupport(){return"undefined"!=typeof document&&void 0!==document.hidden}static determineConnectionMode(){const e=s.config.connectionMode||"auto";return"auto"===e?this.detectSharedWorkerSupport()?(s.logger.info("[MessageSocket] 自动选择 SharedWorker 模式"),"sharedWorker"):this.detectVisibilitySupport()&&s.config.enableVisibilityManagement?(s.logger.info("[MessageSocket] 自动选择 Visibility 模式"),"visibility"):(s.logger.info("[MessageSocket] 自动选择 Normal 模式"),"normal"):"sharedWorker"===e?this.detectSharedWorkerSupport()?(s.logger.info("[MessageSocket] 使用 SharedWorker 模式"),"sharedWorker"):(s.logger.warn("[MessageSocket] 浏览器不支持 SharedWorker,降级到 Visibility 模式"),this.detectVisibilitySupport()&&s.config.enableVisibilityManagement?"visibility":(s.logger.warn("[MessageSocket] 降级到 Normal 模式"),"normal")):"visibility"===e?this.detectVisibilitySupport()?(s.logger.info("[MessageSocket] 使用 Visibility 模式"),"visibility"):(s.logger.warn("[MessageSocket] 浏览器不支持 Visibility API,降级到 Normal 模式"),"normal"):(s.logger.info("[MessageSocket] 使用 Normal 模式"),"normal")}static configure(e){s.config={...s.config,...e}}static setConfig(e){return s.config={...s.config,...e},s.updateLoggerLevel(),s.config.callbacks&&s.config.callbacks.length>0&&s.setCallbacks(s.config.callbacks),s}static setCallbacks(e){return e&&0!==e.length?(s.config.callbacks=e,s):(this.logger.warn("[MessageSocket] 回调列表为空,无法设置回调"),s)}static start(e){if(!s.config.url)return void this.logger.error("[MessageSocket] 缺少配置 url, 请先调用 setConfig 设置配置!");const{userId:t,token:n}=e;t&&n?(this.logger.info("[MessageSocket] 开始连接",t),s.currentMode=s.determineConnectionMode(),"sharedWorker"===s.currentMode?s.startWithSharedWorker(e):"visibility"===s.currentMode?s.startWithVisibility(e):s.startWithNormalMode(e)):this.logger.error("[MessageSocket] 缺少 userId 或 token,无法启动")}static async startWithSharedWorker(e){const{userId:t,token:n}=e;s.stop(),s.currentUserId=t,s.currentToken=n,s.workerManager=new r({url:s.config.url,userId:t,token:n,isVisible:"undefined"==typeof document||!document.hidden,config:s.config,sharedWorkerIdleTimeout:s.config.sharedWorkerIdleTimeout,logLevel:s.config.logLevel,forceNewWorkerOnStart:s.config.forceNewWorkerOnStart}),s.workerManager.onError(t=>{s.logger.warn("[MessageSocket] SharedWorker 失败,降级到 Visibility 模式",t),s.currentMode="visibility",s.workerManager=null,s.startWithVisibility(e)});if(!await s.workerManager.start())return s.logger.warn("[MessageSocket] SharedWorker 启动失败,降级到 Visibility 模式"),s.currentMode="visibility",s.workerManager=null,void s.startWithVisibility(e);s.config.callbacks&&s.config.callbacks.length>0&&s.config.callbacks.forEach(e=>{s.workerManager.registerCallback(e)})}static startWithVisibility(e){const{userId:t,token:r}=e;if(s.client&&s.client.isConnected()&&s.currentUserId===t&&s.currentToken===r)return void this.logger.debug("[MessageSocket] 复用现有连接");s.stop(),s.currentUserId=t,s.currentToken=r;const{url:o,...a}=s.config,i=`${o}/${t}?token=${encodeURIComponent(r)}`;s.client=new n({...a,url:i}),s.config.callbacks&&s.config.callbacks.length>0&&s.config.callbacks.forEach(e=>s.client.on(e)),s.initVisibilityListener(),s.client.connect()}static startWithNormalMode(e){const{userId:t,token:r}=e;if(s.client&&s.client.isConnected()&&s.currentUserId===t&&s.currentToken===r)return void this.logger.debug("[MessageSocket] 复用现有连接");s.stop(),s.currentUserId=t,s.currentToken=r;const{url:o,...a}=s.config,i=`${o}/${t}?token=${encodeURIComponent(r)}`;s.client=new n({...a,url:i}),s.config.callbacks&&s.config.callbacks.length>0&&s.config.callbacks.forEach(e=>s.client.on(e)),s.client.connect()}static stop(){s.logger.info("[MessageSocket] 停止连接"),s.client&&(s.client.disconnect(),s.client.clearCallbacks(),s.client=null),s.workerManager&&(s.workerManager.stop(),s.workerManager=null),s.removeVisibilityListener(),s.currentUserId=null,s.currentToken=null}static registerCallbacks(e){e?"object"==typeof e?"function"==typeof e.callback?"string"==typeof e.type?"sharedWorker"===s.currentMode&&s.workerManager?s.workerManager.registerCallback(e):s.client?s.client.on(e):this.logger.warn("[MessageSocket] 无可用连接,无法注册回调"):this.logger.warn("[MessageSocket] 注册回调失败,type 不是字符串",e):this.logger.warn("[MessageSocket] 注册回调失败,callback 不是函数",e):this.logger.warn("[MessageSocket] 注册回调失败,entry 不是对象",e):this.logger.warn("[MessageSocket] 注册回调失败,缺少 entry",e)}static unregisterCallbacks(e,t){"sharedWorker"===s.currentMode&&s.workerManager?s.workerManager.unregisterCallback(e,t):s.client&&s.client.off(e,t)}static send(e){"sharedWorker"===s.currentMode&&s.workerManager?s.workerManager.send(e):s.client?s.client.send(e):this.logger.warn("[MessageSocket] 无可用连接,无法发送消息")}static getReadyState(){return s.client?.getReadyState()??WebSocket.CLOSED}static isConnected(){return"sharedWorker"===s.currentMode&&s.workerManager?s.workerManager.isConnected():s.client?.isConnected()??!1}static getConnectionMode(){return s.currentMode}static getCurrentUserId(){return s.currentUserId}static getCurrentToken(){return s.currentToken}}s.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,callbacks:[],heartbeatMessage:()=>({type:"PING",timestamp:Date.now()}),logLevel:"warn",enableVisibilityManagement:!1,connectionMode:"auto",sharedWorkerIdleTimeout:3e4,forceNewWorkerOnStart:!1,enableNetworkListener:!0},s.client=null,s.workerManager=null,s.currentMode="normal",s.logger=new t("MessageSocket",s.DEFAULT_CONFIG.logLevel),s.currentUserId=null,s.currentToken=null,s.config={...s.DEFAULT_CONFIG},s.visibilityListenerInitialized=!1,s.handleVisibilityChange=()=>{if("undefined"==typeof document)return;!document.hidden?s.currentUserId&&s.currentToken&&(s.logger.info("[MessageSocket1111111111111111111111] 页面可见,尝试重新连接"),s.client&&s.client.isConnected()||s.start({userId:s.currentUserId,token:s.currentToken})):(s.logger.info("[MessageSocket1111111111111111111111] 页面不可见,断开连接"),s.client&&s.client.disconnect())},exports.MessageSocket=s;
2
2
  //# sourceMappingURL=MessageSocket.cjs.js.map