@brewer/dj-common 1.0.0-beta.0 → 1.0.0-beta.3

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
@@ -22,145 +22,6 @@ yarn add @brewer/dj-common
22
22
  pnpm add @brewer/dj-common
23
23
  ```
24
24
 
25
- ## 功能模块
26
-
27
- ### WebSocketClient - WebSocket 基础封装类
28
-
29
- 通用的 WebSocket 客户端,不依赖于具体业务,提供连接管理、心跳、自动重连等基础功能。
30
-
31
- #### 使用示例
32
-
33
- ```typescript
34
- import { WebSocketClient } from '@brewer/dj-common'
35
-
36
- // 创建实例
37
- const client = new WebSocketClient({
38
- heartbeatInterval: 30000, // 心跳间隔
39
- maxReconnectAttempts: 10, // 最大重连次数
40
- reconnectDelay: 3000, // 重连延迟
41
- autoReconnect: true, // 自动重连
42
- })
43
-
44
- // 注册消息回调
45
- client.on('MESSAGE_TYPE', (data, message) => {
46
- console.log('收到消息:', data)
47
- })
48
-
49
- // 连接
50
- client.connect('ws://example.com/ws')
51
-
52
- // 发送消息
53
- client.send({ type: 'HELLO', data: 'world' })
54
-
55
- // 断开连接
56
- client.disconnect()
57
- ```
58
-
59
- #### API 说明
60
-
61
- ##### 配置选项
62
-
63
- ```typescript
64
- interface WebSocketConfig {
65
- url?: string // WebSocket 服务器地址
66
- heartbeatInterval?: number // 心跳间隔(毫秒),默认 25000
67
- maxReconnectAttempts?: number // 最大重连次数,默认 10
68
- reconnectDelay?: number // 重连延迟(毫秒),默认 3000
69
- reconnectDelayMax?: number // 最大重连延迟(毫秒),默认 10000
70
- heartbeatMessage?: () => string | object // 心跳消息生成器
71
- autoReconnect?: boolean // 是否自动重连,默认 true
72
- }
73
- ```
74
-
75
- ##### 方法
76
-
77
- - `connect(url?: string): void` - 连接到 WebSocket 服务器
78
- - `disconnect(): void` - 断开连接
79
- - `send(data: string | object): void` - 发送消息
80
- - `on(type, callback): void` - 注册消息回调
81
- - `off(type, callback?): void` - 取消注册消息回调
82
- - `clearCallbacks(): void` - 清空所有回调
83
- - `isConnected(): boolean` - 是否已连接
84
- - `getReadyState(): number` - 获取当前连接状态
85
-
86
- ---
87
-
88
- ### MessageSocket - 消息 Socket 管理类
89
-
90
- 基于 WebSocketClient 的用户消息管理类,适用于需要用户认证的场景(如获取用户未读消息数量)。
91
-
92
- #### 使用示例
93
-
94
- ```typescript
95
- import { MessageSocket } from '@brewer/dj-common'
96
-
97
- // 可选:自定义配置
98
- MessageSocket.configure({
99
- baseUrl: 'ws://your-server.com',
100
- path: '/your/path',
101
- heartbeatInterval: 30000,
102
- })
103
-
104
- // 启动连接
105
- MessageSocket.start({
106
- userId: '1234567890',
107
- token: 'your-token',
108
- callbacks: [
109
- {
110
- type: 'UNREAD_COUNT',
111
- callback: (payload) => {
112
- console.log('未读消息数:', payload)
113
- },
114
- },
115
- {
116
- type: 'NEW_MESSAGE',
117
- callback: (payload) => {
118
- console.log('新消息:', payload)
119
- },
120
- },
121
- ],
122
- })
123
-
124
- // 动态注册回调
125
- MessageSocket.registerCallbacks({
126
- type: 'NOTIFICATION',
127
- callback: (payload) => {
128
- console.log('通知:', payload)
129
- },
130
- })
131
-
132
- // 发送消息
133
- MessageSocket.send({ type: 'MARK_READ', messageId: '123' })
134
-
135
- // 停止连接
136
- MessageSocket.stop()
137
- ```
138
-
139
- #### API 说明
140
-
141
- ##### 配置选项
142
-
143
- ```typescript
144
- interface MessageSocketConfig extends WebSocketConfig {
145
- baseUrl?: string // WebSocket 服务器基础地址,默认 'ws://dev-gateway.chinamarket.cn'
146
- path?: string // WebSocket 路径,默认 '/api/user-web/websocket/messageServer'
147
- }
148
- ```
149
-
150
- ##### 方法
151
-
152
- - `configure(config): void` - 配置 MessageSocket
153
- - `start(options): void` - 启动连接
154
- - `stop(): void` - 停止连接
155
- - `registerCallbacks(entry): void` - 注册消息回调
156
- - `unregisterCallbacks(type, callback?): void` - 取消注册消息回调
157
- - `send(data): void` - 发送消息
158
- - `isConnected(): boolean` - 是否已连接
159
- - `getCurrentUserId(): string | null` - 获取当前用户ID
160
- - `getCurrentToken(): string | null` - 获取当前token
161
-
162
- ---
163
-
164
25
  ## 按需引入
165
26
 
166
27
  你可以单独引入某个模块:
@@ -1,2 +1,2 @@
1
- "use strict";class e{constructor(t={}){this.socket=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.callbackList=[],this.currentUrl=null,this.reconnectAttempts=0,this.manualClose=!1,this.config={...e.DEFAULT_CONFIG,...t}}updateConfig(e){this.config={...this.config,...e}}connect(e){const t=e||this.config.url;if(t){this.currentUrl=t,this.manualClose=!1;try{this.socket=new WebSocket(t),this.socket.onopen=()=>{console.log("[WebSocketClient] 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),this.onOpen()},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{console.log("[WebSocketClient] 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.onClose(e),!this.manualClose&&this.config.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{console.error("[WebSocketClient] 连接错误",e),this.stopHeartbeat(),this.onError(e)}}catch(e){console.error("[WebSocketClient] 连接失败",e),this.config.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else console.error("[WebSocketClient] 缺少 WebSocket URL")}scheduleReconnect(){if(this.reconnectAttempts>=this.config.maxReconnectAttempts||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=this.config.maxReconnectAttempts&&console.warn("[WebSocketClient] 已达到最大重连次数"));this.reconnectAttempts+=1;const e=Math.min(this.config.reconnectDelay*this.reconnectAttempts,this.config.reconnectDelayMax);console.log(`[WebSocketClient] 将在 ${e}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=window.setTimeout(()=>{this.connect(this.currentUrl)},e)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.currentUrl=null,this.reconnectAttempts=0}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void console.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 console.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){console.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):console.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(){}onClose(e){}onError(e){}onMessage(e){}}e.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,heartbeatMessage:()=>({type:"PING",timestamp:Date.now()})};class t{static configure(e){t.config={...t.config,...e}}static start(n){const{userId:c,token:s,callbacks:o=[]}=n;if(!c||!s)return void console.warn("[MessageSocket] 缺少 userId 或 token,无法启动");if(t.client&&t.client.isConnected()&&t.currentUserId===c&&t.currentToken===s)return console.log("[MessageSocket] 复用现有连接"),void o.forEach(e=>t.registerCallbacks(e));t.stop(),t.currentUserId=c,t.currentToken=s;const{baseUrl:r,path:i,...a}=t.config,l=`${r}${i}/${c}?token=${encodeURIComponent(s)}`;t.client=new e({...a,url:l}),o.forEach(e=>t.registerCallbacks(e)),t.client.connect()}static stop(){t.client&&(t.client.disconnect(),t.client.clearCallbacks(),t.client=null),t.currentUserId=null,t.currentToken=null}static registerCallbacks(e){e?"object"==typeof e?"function"==typeof e.callback?"string"==typeof e.type?t.client?t.client.on(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法注册回调"):console.warn("[MessageSocket] 注册回调失败,type 不是字符串",e):console.warn("[MessageSocket] 注册回调失败,callback 不是函数",e):console.warn("[MessageSocket] 注册回调失败,entry 不是对象",e):console.warn("[MessageSocket] 注册回调失败,缺少 entry",e)}static unregisterCallbacks(e,n){t.client&&t.client.off(e,n)}static send(e){t.client?t.client.send(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法发送消息")}static getReadyState(){return t.client?.getReadyState()??WebSocket.CLOSED}static isConnected(){return t.client?.isConnected()??!1}static getCurrentUserId(){return t.currentUserId}static getCurrentToken(){return t.currentToken}}t.DEFAULT_CONFIG={baseUrl:"ws://dev-gateway.chinamarket.cn",path:"/api/user-web/websocket/messageServer",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0},t.client=null,t.currentUserId=null,t.currentToken=null,t.config={...t.DEFAULT_CONFIG},exports.MessageSocket=t;
1
+ "use strict";class e{constructor(t={}){this.socket=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.callbackList=[],this.currentUrl=null,this.reconnectAttempts=0,this.manualClose=!1,this.config={...e.DEFAULT_CONFIG,...t}}updateConfig(e){this.config={...this.config,...e}}connect(e){const t=e||this.config.url;if(t){this.currentUrl=t,this.manualClose=!1;try{this.socket=new WebSocket(t),this.socket.onopen=()=>{console.log("[WebSocketClient] 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),this.onOpen()},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{console.log("[WebSocketClient] 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.onClose(e),!this.manualClose&&this.config.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{console.error("[WebSocketClient] 连接错误",e),this.stopHeartbeat(),this.onError(e)}}catch(e){console.error("[WebSocketClient] 连接失败",e),this.config.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else console.error("[WebSocketClient] 缺少 WebSocket URL")}scheduleReconnect(){if(this.reconnectAttempts>=this.config.maxReconnectAttempts||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=this.config.maxReconnectAttempts&&console.warn("[WebSocketClient] 已达到最大重连次数"));this.reconnectAttempts+=1;const e=Math.min(this.config.reconnectDelay*this.reconnectAttempts,this.config.reconnectDelayMax);console.log(`[WebSocketClient] 将在 ${e}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=window.setTimeout(()=>{this.connect(this.currentUrl)},e)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.currentUrl=null,this.reconnectAttempts=0}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void console.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 console.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){console.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):console.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(){}onClose(e){}onError(e){}onMessage(e){}}e.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,heartbeatMessage:()=>({type:"PING",timestamp:Date.now()})};class t{static configure(e){t.config={...t.config,...e}}static start(n){const{userId:c,token:s,callbacks:o=[]}=n;if(!c||!s)return void console.warn("[MessageSocket] 缺少 userId 或 token,无法启动");if(t.client&&t.client.isConnected()&&t.currentUserId===c&&t.currentToken===s)return console.log("[MessageSocket] 复用现有连接"),void o.forEach(e=>t.registerCallbacks(e));t.stop(),t.currentUserId=c,t.currentToken=s;const{baseUrl:r,path:i,...a}=t.config,l=`${r}${i}/${c}?token=${encodeURIComponent(s)}`;t.client=new e({...a,url:l}),o.forEach(e=>t.registerCallbacks(e)),t.client.connect()}static stop(){t.client&&(t.client.disconnect(),t.client.clearCallbacks(),t.client=null),t.currentUserId=null,t.currentToken=null}static registerCallbacks(e){e?"object"==typeof e?"function"==typeof e.callback?"string"==typeof e.type?t.client?t.client.on(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法注册回调"):console.warn("[MessageSocket] 注册回调失败,type 不是字符串",e):console.warn("[MessageSocket] 注册回调失败,callback 不是函数",e):console.warn("[MessageSocket] 注册回调失败,entry 不是对象",e):console.warn("[MessageSocket] 注册回调失败,缺少 entry",e)}static unregisterCallbacks(e,n){t.client&&t.client.off(e,n)}static send(e){t.client?t.client.send(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法发送消息")}static getReadyState(){return t.client?.getReadyState()??WebSocket.CLOSED}static isConnected(){return t.client?.isConnected()??!1}static getCurrentUserId(){return t.currentUserId}static getCurrentToken(){return t.currentToken}}t.DEFAULT_CONFIG={baseUrl:"wss://dev-gateway.chinamarket.cn",path:"/api/user-web/websocket/messageServer",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0},t.client=null,t.currentUserId=null,t.currentToken=null,t.config={...t.DEFAULT_CONFIG},exports.MessageSocket=t;
2
2
  //# sourceMappingURL=MessageSocket.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageSocket.cjs.js","sources":["../src/WebSocketClient.ts","../src/MessageSocket.ts"],"sourcesContent":["/**\n * WebSocket 配置选项\n */\nexport interface WebSocketConfig {\n /** WebSocket 服务器地址(可选,可以在 connect 时指定) */\n url?: string\n /** 心跳间隔(毫秒),默认 25000 */\n heartbeatInterval?: number\n /** 最大重连次数,默认 10 */\n maxReconnectAttempts?: number\n /** 重连延迟(毫秒),默认 3000 */\n reconnectDelay?: number\n /** 最大重连延迟(毫秒),默认 10000 */\n reconnectDelayMax?: number\n /** 心跳消息生成器 */\n heartbeatMessage?: () => string | object\n /** 是否自动重连,默认 true */\n autoReconnect?: boolean\n}\n\n/**\n * 消息数据结构\n */\nexport interface MessageData<T = unknown> {\n /** 消息类型 */\n type: string\n /** 消息数据 */\n data: T\n /** 元数据 */\n meta?: Record<string, unknown>\n /** 时间戳 */\n timestamp?: number\n}\n\n/**\n * 消息回调函数\n */\nexport type MessageCallback<T = unknown> = (data: T, message?: MessageData<T>) => void\n\n/**\n * 消息回调配置\n */\nexport interface MessageCallbackEntry<T = unknown> {\n /** 消息类型 */\n type: string\n /** 回调函数 */\n callback: MessageCallback<T>\n}\n\n/**\n * WebSocket 基础封装类\n * 提供连接管理、心跳、自动重连、消息回调等基础功能\n */\nexport class WebSocketClient {\n /** WebSocket 连接实例 */\n protected socket: WebSocket | null = null\n\n /** 心跳定时器 */\n protected heartbeatTimer: number | null = null\n\n /** 重连定时器 */\n protected reconnectTimer: number | null = null\n\n /** 消息回调列表 */\n protected callbackList: MessageCallbackEntry<unknown>[] = []\n\n /** 当前连接的 URL */\n protected currentUrl: string | null = null\n\n /** 配置选项 */\n protected config: Required<WebSocketConfig>\n\n /** 重连次数 */\n protected reconnectAttempts = 0\n\n /** 是否手动关闭 */\n protected manualClose = false\n\n /**\n * 默认配置\n */\n protected static readonly DEFAULT_CONFIG: Required<WebSocketConfig> = {\n url: '',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n heartbeatMessage: () => ({\n type: 'PING',\n timestamp: Date.now(),\n }),\n }\n\n /**\n * 构造函数\n * @param config 配置选项\n */\n constructor(config: WebSocketConfig = {}) {\n this.config = { ...WebSocketClient.DEFAULT_CONFIG, ...config }\n }\n\n /**\n * 更新配置\n * @param config 新的配置选项\n */\n public updateConfig(config: Partial<WebSocketConfig>): void {\n this.config = { ...this.config, ...config }\n }\n\n /**\n * 连接到 WebSocket 服务器\n * @param url WebSocket 地址(可选,如果不传则使用配置中的 url)\n */\n public connect(url?: string): void {\n const targetUrl = url || this.config.url\n if (!targetUrl) {\n console.error('[WebSocketClient] 缺少 WebSocket URL')\n return\n }\n\n this.currentUrl = targetUrl\n this.manualClose = false\n\n try {\n this.socket = new WebSocket(targetUrl)\n\n this.socket.onopen = () => {\n console.log('[WebSocketClient] 连接成功')\n this.reconnectAttempts = 0\n this.startHeartbeat()\n this.onOpen()\n }\n\n this.socket.onmessage = (event: MessageEvent) => {\n this.handleIncoming(event.data)\n }\n\n this.socket.onclose = (event: CloseEvent) => {\n console.log('[WebSocketClient] 连接关闭', event.code, event.reason)\n this.stopHeartbeat()\n this.onClose(event)\n\n if (!this.manualClose && this.config.autoReconnect) {\n this.scheduleReconnect()\n }\n }\n\n this.socket.onerror = (event: Event) => {\n console.error('[WebSocketClient] 连接错误', event)\n this.stopHeartbeat()\n this.onError(event)\n }\n } catch (error) {\n console.error('[WebSocketClient] 连接失败', error)\n if (this.config.autoReconnect && !this.manualClose) {\n this.scheduleReconnect()\n }\n }\n }\n\n /**\n * 计划重连\n */\n protected scheduleReconnect(): void {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {\n console.warn('[WebSocketClient] 已达到最大重连次数')\n }\n return\n }\n\n this.reconnectAttempts += 1\n const delay = Math.min(this.config.reconnectDelay * this.reconnectAttempts, this.config.reconnectDelayMax)\n\n console.log(`[WebSocketClient] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`)\n\n this.reconnectTimer = window.setTimeout(() => {\n this.connect(this.currentUrl!)\n }, delay)\n }\n\n /**\n * 断开连接\n */\n public disconnect(): void {\n this.manualClose = true\n this.stopHeartbeat()\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n\n if (this.socket) {\n this.socket.close()\n this.socket = null\n }\n\n this.currentUrl = null\n this.reconnectAttempts = 0\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public send(data: string | object): void {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n console.warn('[WebSocketClient] WebSocket 未连接,无法发送消息')\n return\n }\n\n const message = typeof data === 'string' ? data : JSON.stringify(data)\n this.socket.send(message)\n }\n\n /**\n * 处理接收的消息\n * @param data 接收到的数据\n */\n protected handleIncoming(data: string): void {\n if (!data) return\n\n let message: MessageData\n try {\n message = JSON.parse(data)\n } catch {\n console.warn('[WebSocketClient] 无法解析消息', data)\n return\n }\n\n if (!message?.type) {\n return\n }\n\n // 触发匹配的回调\n const matched = this.callbackList.filter((entry) => entry.type === message.type)\n matched.forEach(({ callback }) => {\n try {\n callback(message.data, message)\n } catch (error) {\n console.error('[WebSocketClient] 回调执行失败', error)\n }\n })\n\n // 调用钩子\n this.onMessage(message)\n }\n\n /**\n * 启动心跳\n */\n protected startHeartbeat(): void {\n this.stopHeartbeat()\n\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n this.heartbeatTimer = window.setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n const heartbeatData = this.config.heartbeatMessage()\n this.send(heartbeatData)\n }, this.config.heartbeatInterval)\n }\n\n /**\n * 停止心跳\n */\n protected stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public on<T = unknown>(type: string, callback: MessageCallback<T>): void\n public on<T = unknown>(entry: MessageCallbackEntry<T>): void\n public on<T = unknown>(typeOrEntry: string | MessageCallbackEntry<T>, callback?: MessageCallback<T>): void {\n const entry: MessageCallbackEntry<T> =\n typeof typeOrEntry === 'string' ? { type: typeOrEntry, callback: callback! } : typeOrEntry\n\n if (!entry.type || typeof entry.callback !== 'function') {\n console.warn('[WebSocketClient] 无效的回调配置', entry)\n return\n }\n\n this.callbackList.push(entry as MessageCallbackEntry<unknown>)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选,如果不传则移除该类型的所有回调)\n */\n public off<T = unknown>(type: string, callback?: MessageCallback<T>): void {\n if (callback) {\n this.callbackList = this.callbackList.filter((entry) => !(entry.type === type && entry.callback === callback))\n } else {\n this.callbackList = this.callbackList.filter((entry) => entry.type !== type)\n }\n }\n\n /**\n * 清空所有回调\n */\n public clearCallbacks(): void {\n this.callbackList = []\n }\n\n /**\n * 获取当前连接状态\n */\n public getReadyState(): number {\n return this.socket?.readyState ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public isConnected(): boolean {\n return this.socket?.readyState === WebSocket.OPEN\n }\n\n // ========== 钩子方法(子类可以重写) ==========\n\n /**\n * 连接打开时的钩子\n */\n protected onOpen(): void {\n // 子类可以重写\n }\n\n /**\n * 连接关闭时的钩子\n */\n protected onClose(_event: CloseEvent): void {\n // 子类可以重写\n }\n\n /**\n * 连接错误时的钩子\n */\n protected onError(_event: Event): void {\n // 子类可以重写\n }\n\n /**\n * 收到消息时的钩子\n */\n protected onMessage(_message: MessageData): void {\n // 子类可以重写\n }\n}\n","import { WebSocketClient, WebSocketConfig, MessageCallbackEntry } from './WebSocketClient'\n\n/**\n * MessageSocket 配置选项\n */\nexport interface MessageSocketConfig extends WebSocketConfig {\n /** WebSocket 服务器基础地址,默认 'ws://dev-gateway.chinamarket.cn' */\n baseUrl?: string\n /** WebSocket 路径,默认 '/api/user-web/websocket/messageServer' */\n path?: string\n}\n\n/**\n * MessageSocket 启动选项\n */\nexport interface MessageSocketStartOptions {\n /** 用户ID(必需) */\n userId: string\n /** 认证令牌(必需) */\n token: string\n /** 消息回调列表 */\n callbacks?: MessageCallbackEntry[]\n}\n\n/**\n * 消息 Socket 类\n * 基于 WebSocketClient,提供用户认证和消息管理功能\n * 用于获取用户未读消息数量等业务场景\n *\n * @example\n * ```typescript\n * // 启动连接\n * MessageSocket.start({\n * userId: '1234567890',\n * token: 'your-token',\n * callbacks: [\n * {\n * type: 'UNREAD_COUNT',\n * callback: (payload) => {\n * console.log('未读消息数:', payload)\n * }\n * }\n * ]\n * })\n *\n * // 停止连接\n * MessageSocket.stop()\n *\n * // 动态注册回调\n * MessageSocket.registerCallbacks({\n * type: 'NEW_MESSAGE',\n * callback: (payload) => {\n * console.log('新消息:', payload)\n * }\n * })\n * ```\n */\nexport class MessageSocket {\n /** 默认配置 */\n private static readonly DEFAULT_CONFIG: MessageSocketConfig = {\n baseUrl: 'ws://dev-gateway.chinamarket.cn',\n path: '/api/user-web/websocket/messageServer',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n }\n\n /** WebSocket 客户端实例 */\n private static client: WebSocketClient | null = null\n\n /** 当前用户ID */\n private static currentUserId: string | null = null\n\n /** 当前token */\n private static currentToken: string | null = null\n\n /** 当前配置 */\n private static config: MessageSocketConfig = { ...MessageSocket.DEFAULT_CONFIG }\n\n /**\n * 配置 MessageSocket\n * @param config 配置选项\n */\n public static configure(config: Partial<MessageSocketConfig>): void {\n MessageSocket.config = { ...MessageSocket.config, ...config }\n }\n\n /**\n * 启动连接\n * @param options 启动选项\n */\n public static start(options: MessageSocketStartOptions): void {\n const { userId, token, callbacks = [] } = options\n\n if (!userId || !token) {\n console.warn('[MessageSocket] 缺少 userId 或 token,无法启动')\n return\n }\n\n // 检查是否可以复用现有连接\n const shouldReuse =\n MessageSocket.client &&\n MessageSocket.client.isConnected() &&\n MessageSocket.currentUserId === userId &&\n MessageSocket.currentToken === token\n\n if (shouldReuse) {\n console.log('[MessageSocket] 复用现有连接')\n // 重新注册回调\n callbacks.forEach((entry) => MessageSocket.registerCallbacks(entry))\n return\n }\n\n // 停止旧连接\n MessageSocket.stop()\n\n // 保存当前用户信息\n MessageSocket.currentUserId = userId\n MessageSocket.currentToken = token\n\n // 构建 WebSocket URL\n const { baseUrl, path, ...clientConfig } = MessageSocket.config\n const url = `${baseUrl}${path}/${userId}?token=${encodeURIComponent(token)}`\n\n // 创建新的 WebSocket 客户端\n MessageSocket.client = new WebSocketClient({\n ...clientConfig,\n url,\n })\n\n // 注册回调\n callbacks.forEach((entry) => MessageSocket.registerCallbacks(entry))\n\n // 连接\n MessageSocket.client.connect()\n }\n\n /**\n * 停止连接\n */\n public static stop(): void {\n if (MessageSocket.client) {\n MessageSocket.client.disconnect()\n MessageSocket.client.clearCallbacks()\n MessageSocket.client = null\n }\n\n MessageSocket.currentUserId = null\n MessageSocket.currentToken = null\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public static registerCallbacks<T = unknown>(entry: MessageCallbackEntry<T>): void {\n if (!entry) {\n console.warn('[MessageSocket] 注册回调失败,缺少 entry', entry)\n return\n }\n\n if (typeof entry !== 'object') {\n console.warn('[MessageSocket] 注册回调失败,entry 不是对象', entry)\n return\n }\n\n if (typeof entry.callback !== 'function') {\n console.warn('[MessageSocket] 注册回调失败,callback 不是函数', entry)\n return\n }\n\n if (typeof entry.type !== 'string') {\n console.warn('[MessageSocket] 注册回调失败,type 不是字符串', entry)\n return\n }\n\n if (!MessageSocket.client) {\n console.warn('[MessageSocket] WebSocket 客户端未初始化,无法注册回调')\n return\n }\n\n MessageSocket.client.on(entry)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选)\n */\n public static unregisterCallbacks<T = unknown>(type: string, callback?: (data: T, message?: unknown) => void): void {\n if (!MessageSocket.client) {\n return\n }\n\n MessageSocket.client.off(type, callback)\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public static send(data: string | object): void {\n if (!MessageSocket.client) {\n console.warn('[MessageSocket] WebSocket 客户端未初始化,无法发送消息')\n return\n }\n\n MessageSocket.client.send(data)\n }\n\n /**\n * 获取连接状态\n */\n public static getReadyState(): number {\n return MessageSocket.client?.getReadyState() ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public static isConnected(): boolean {\n return MessageSocket.client?.isConnected() ?? false\n }\n\n /**\n * 获取当前用户ID\n */\n public static getCurrentUserId(): string | null {\n return MessageSocket.currentUserId\n }\n\n /**\n * 获取当前token\n */\n public static getCurrentToken(): string | null {\n return MessageSocket.currentToken\n }\n}\n"],"names":["WebSocketClient","constructor","config","this","socket","heartbeatTimer","reconnectTimer","callbackList","currentUrl","reconnectAttempts","manualClose","DEFAULT_CONFIG","updateConfig","connect","url","targetUrl","WebSocket","onopen","console","log","startHeartbeat","onOpen","onmessage","event","handleIncoming","data","onclose","code","reason","stopHeartbeat","onClose","autoReconnect","scheduleReconnect","onerror","error","onError","maxReconnectAttempts","warn","delay","Math","min","reconnectDelay","reconnectDelayMax","window","setTimeout","disconnect","clearTimeout","close","send","readyState","OPEN","message","JSON","stringify","parse","type","filter","entry","forEach","callback","onMessage","setInterval","heartbeatData","heartbeatMessage","heartbeatInterval","clearInterval","on","typeOrEntry","push","off","clearCallbacks","getReadyState","CLOSED","isConnected","_event","_message","timestamp","Date","now","MessageSocket","configure","start","options","userId","token","callbacks","client","currentUserId","currentToken","registerCallbacks","stop","baseUrl","path","clientConfig","encodeURIComponent","unregisterCallbacks","getCurrentUserId","getCurrentToken"],"mappings":"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"}
1
+ {"version":3,"file":"MessageSocket.cjs.js","sources":["../src/WebSocketClient.ts","../src/MessageSocket.ts"],"sourcesContent":["/**\n * WebSocket 配置选项\n */\nexport interface WebSocketConfig {\n /** WebSocket 服务器地址(可选,可以在 connect 时指定) */\n url?: string\n /** 心跳间隔(毫秒),默认 25000 */\n heartbeatInterval?: number\n /** 最大重连次数,默认 10 */\n maxReconnectAttempts?: number\n /** 重连延迟(毫秒),默认 3000 */\n reconnectDelay?: number\n /** 最大重连延迟(毫秒),默认 10000 */\n reconnectDelayMax?: number\n /** 心跳消息生成器 */\n heartbeatMessage?: () => string | object\n /** 是否自动重连,默认 true */\n autoReconnect?: boolean\n}\n\n/**\n * 消息数据结构\n */\nexport interface MessageData<T = unknown> {\n /** 消息类型 */\n type: string\n /** 消息数据 */\n data: T\n /** 元数据 */\n meta?: Record<string, unknown>\n /** 时间戳 */\n timestamp?: number\n}\n\n/**\n * 消息回调函数\n */\nexport type MessageCallback<T = unknown> = (data: T, message?: MessageData<T>) => void\n\n/**\n * 消息回调配置\n */\nexport interface MessageCallbackEntry<T = unknown> {\n /** 消息类型 */\n type: string\n /** 回调函数 */\n callback: MessageCallback<T>\n}\n\n/**\n * WebSocket 基础封装类\n * 提供连接管理、心跳、自动重连、消息回调等基础功能\n */\nexport class WebSocketClient {\n /** WebSocket 连接实例 */\n protected socket: WebSocket | null = null\n\n /** 心跳定时器 */\n protected heartbeatTimer: number | null = null\n\n /** 重连定时器 */\n protected reconnectTimer: number | null = null\n\n /** 消息回调列表 */\n protected callbackList: MessageCallbackEntry<unknown>[] = []\n\n /** 当前连接的 URL */\n protected currentUrl: string | null = null\n\n /** 配置选项 */\n protected config: Required<WebSocketConfig>\n\n /** 重连次数 */\n protected reconnectAttempts = 0\n\n /** 是否手动关闭 */\n protected manualClose = false\n\n /**\n * 默认配置\n */\n protected static readonly DEFAULT_CONFIG: Required<WebSocketConfig> = {\n url: '',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n heartbeatMessage: () => ({\n type: 'PING',\n timestamp: Date.now(),\n }),\n }\n\n /**\n * 构造函数\n * @param config 配置选项\n */\n constructor(config: WebSocketConfig = {}) {\n this.config = { ...WebSocketClient.DEFAULT_CONFIG, ...config }\n }\n\n /**\n * 更新配置\n * @param config 新的配置选项\n */\n public updateConfig(config: Partial<WebSocketConfig>): void {\n this.config = { ...this.config, ...config }\n }\n\n /**\n * 连接到 WebSocket 服务器\n * @param url WebSocket 地址(可选,如果不传则使用配置中的 url)\n */\n public connect(url?: string): void {\n const targetUrl = url || this.config.url\n if (!targetUrl) {\n console.error('[WebSocketClient] 缺少 WebSocket URL')\n return\n }\n\n this.currentUrl = targetUrl\n this.manualClose = false\n\n try {\n this.socket = new WebSocket(targetUrl)\n\n this.socket.onopen = () => {\n console.log('[WebSocketClient] 连接成功')\n this.reconnectAttempts = 0\n this.startHeartbeat()\n this.onOpen()\n }\n\n this.socket.onmessage = (event: MessageEvent) => {\n this.handleIncoming(event.data)\n }\n\n this.socket.onclose = (event: CloseEvent) => {\n console.log('[WebSocketClient] 连接关闭', event.code, event.reason)\n this.stopHeartbeat()\n this.onClose(event)\n\n if (!this.manualClose && this.config.autoReconnect) {\n this.scheduleReconnect()\n }\n }\n\n this.socket.onerror = (event: Event) => {\n console.error('[WebSocketClient] 连接错误', event)\n this.stopHeartbeat()\n this.onError(event)\n }\n } catch (error) {\n console.error('[WebSocketClient] 连接失败', error)\n if (this.config.autoReconnect && !this.manualClose) {\n this.scheduleReconnect()\n }\n }\n }\n\n /**\n * 计划重连\n */\n protected scheduleReconnect(): void {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {\n console.warn('[WebSocketClient] 已达到最大重连次数')\n }\n return\n }\n\n this.reconnectAttempts += 1\n const delay = Math.min(this.config.reconnectDelay * this.reconnectAttempts, this.config.reconnectDelayMax)\n\n console.log(`[WebSocketClient] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`)\n\n this.reconnectTimer = window.setTimeout(() => {\n this.connect(this.currentUrl!)\n }, delay)\n }\n\n /**\n * 断开连接\n */\n public disconnect(): void {\n this.manualClose = true\n this.stopHeartbeat()\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n\n if (this.socket) {\n this.socket.close()\n this.socket = null\n }\n\n this.currentUrl = null\n this.reconnectAttempts = 0\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public send(data: string | object): void {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n console.warn('[WebSocketClient] WebSocket 未连接,无法发送消息')\n return\n }\n\n const message = typeof data === 'string' ? data : JSON.stringify(data)\n this.socket.send(message)\n }\n\n /**\n * 处理接收的消息\n * @param data 接收到的数据\n */\n protected handleIncoming(data: string): void {\n if (!data) return\n\n let message: MessageData\n try {\n message = JSON.parse(data)\n } catch {\n console.warn('[WebSocketClient] 无法解析消息', data)\n return\n }\n\n if (!message?.type) {\n return\n }\n\n // 触发匹配的回调\n const matched = this.callbackList.filter((entry) => entry.type === message.type)\n matched.forEach(({ callback }) => {\n try {\n callback(message.data, message)\n } catch (error) {\n console.error('[WebSocketClient] 回调执行失败', error)\n }\n })\n\n // 调用钩子\n this.onMessage(message)\n }\n\n /**\n * 启动心跳\n */\n protected startHeartbeat(): void {\n this.stopHeartbeat()\n\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n this.heartbeatTimer = window.setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n const heartbeatData = this.config.heartbeatMessage()\n this.send(heartbeatData)\n }, this.config.heartbeatInterval)\n }\n\n /**\n * 停止心跳\n */\n protected stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public on<T = unknown>(type: string, callback: MessageCallback<T>): void\n public on<T = unknown>(entry: MessageCallbackEntry<T>): void\n public on<T = unknown>(typeOrEntry: string | MessageCallbackEntry<T>, callback?: MessageCallback<T>): void {\n const entry: MessageCallbackEntry<T> =\n typeof typeOrEntry === 'string' ? { type: typeOrEntry, callback: callback! } : typeOrEntry\n\n if (!entry.type || typeof entry.callback !== 'function') {\n console.warn('[WebSocketClient] 无效的回调配置', entry)\n return\n }\n\n this.callbackList.push(entry as MessageCallbackEntry<unknown>)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选,如果不传则移除该类型的所有回调)\n */\n public off<T = unknown>(type: string, callback?: MessageCallback<T>): void {\n if (callback) {\n this.callbackList = this.callbackList.filter((entry) => !(entry.type === type && entry.callback === callback))\n } else {\n this.callbackList = this.callbackList.filter((entry) => entry.type !== type)\n }\n }\n\n /**\n * 清空所有回调\n */\n public clearCallbacks(): void {\n this.callbackList = []\n }\n\n /**\n * 获取当前连接状态\n */\n public getReadyState(): number {\n return this.socket?.readyState ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public isConnected(): boolean {\n return this.socket?.readyState === WebSocket.OPEN\n }\n\n // ========== 钩子方法(子类可以重写) ==========\n\n /**\n * 连接打开时的钩子\n */\n protected onOpen(): void {\n // 子类可以重写\n }\n\n /**\n * 连接关闭时的钩子\n */\n protected onClose(_event: CloseEvent): void {\n // 子类可以重写\n }\n\n /**\n * 连接错误时的钩子\n */\n protected onError(_event: Event): void {\n // 子类可以重写\n }\n\n /**\n * 收到消息时的钩子\n */\n protected onMessage(_message: MessageData): void {\n // 子类可以重写\n }\n}\n","import { WebSocketClient, WebSocketConfig, MessageCallbackEntry } from './WebSocketClient'\n\n/**\n * MessageSocket 配置选项\n */\nexport interface MessageSocketConfig extends WebSocketConfig {\n /** WebSocket 服务器基础地址,默认 'ws://dev-gateway.chinamarket.cn' */\n baseUrl?: string\n /** WebSocket 路径,默认 '/api/user-web/websocket/messageServer' */\n path?: string\n}\n\n/**\n * MessageSocket 启动选项\n */\nexport interface MessageSocketStartOptions {\n /** 用户ID(必需) */\n userId: string\n /** 认证令牌(必需) */\n token: string\n /** 消息回调列表 */\n callbacks?: MessageCallbackEntry[]\n}\n\n/**\n * 消息 Socket 类\n * 基于 WebSocketClient,提供用户认证和消息管理功能\n * 用于获取用户未读消息数量等业务场景\n *\n * @example\n * ```typescript\n * // 启动连接\n * MessageSocket.start({\n * userId: '1234567890',\n * token: 'your-token',\n * callbacks: [\n * {\n * type: 'UNREAD_COUNT',\n * callback: (payload) => {\n * console.log('未读消息数:', payload)\n * }\n * }\n * ]\n * })\n *\n * // 停止连接\n * MessageSocket.stop()\n *\n * // 动态注册回调\n * MessageSocket.registerCallbacks({\n * type: 'NEW_MESSAGE',\n * callback: (payload) => {\n * console.log('新消息:', payload)\n * }\n * })\n * ```\n */\nexport class MessageSocket {\n /** 默认配置 */\n private static readonly DEFAULT_CONFIG: MessageSocketConfig = {\n baseUrl: 'wss://dev-gateway.chinamarket.cn',\n path: '/api/user-web/websocket/messageServer',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n }\n\n /** WebSocket 客户端实例 */\n private static client: WebSocketClient | null = null\n\n /** 当前用户ID */\n private static currentUserId: string | null = null\n\n /** 当前token */\n private static currentToken: string | null = null\n\n /** 当前配置 */\n private static config: MessageSocketConfig = { ...MessageSocket.DEFAULT_CONFIG }\n\n /**\n * 配置 MessageSocket\n * @param config 配置选项\n */\n public static configure(config: Partial<MessageSocketConfig>): void {\n MessageSocket.config = { ...MessageSocket.config, ...config }\n }\n\n /**\n * 启动连接\n * @param options 启动选项\n */\n public static start(options: MessageSocketStartOptions): void {\n const { userId, token, callbacks = [] } = options\n\n if (!userId || !token) {\n console.warn('[MessageSocket] 缺少 userId 或 token,无法启动')\n return\n }\n\n // 检查是否可以复用现有连接\n const shouldReuse =\n MessageSocket.client &&\n MessageSocket.client.isConnected() &&\n MessageSocket.currentUserId === userId &&\n MessageSocket.currentToken === token\n\n if (shouldReuse) {\n console.log('[MessageSocket] 复用现有连接')\n // 重新注册回调\n callbacks.forEach((entry) => MessageSocket.registerCallbacks(entry))\n return\n }\n\n // 停止旧连接\n MessageSocket.stop()\n\n // 保存当前用户信息\n MessageSocket.currentUserId = userId\n MessageSocket.currentToken = token\n\n // 构建 WebSocket URL\n const { baseUrl, path, ...clientConfig } = MessageSocket.config\n const url = `${baseUrl}${path}/${userId}?token=${encodeURIComponent(token)}`\n\n // 创建新的 WebSocket 客户端\n MessageSocket.client = new WebSocketClient({\n ...clientConfig,\n url,\n })\n\n // 注册回调\n callbacks.forEach((entry) => MessageSocket.registerCallbacks(entry))\n\n // 连接\n MessageSocket.client.connect()\n }\n\n /**\n * 停止连接\n */\n public static stop(): void {\n if (MessageSocket.client) {\n MessageSocket.client.disconnect()\n MessageSocket.client.clearCallbacks()\n MessageSocket.client = null\n }\n\n MessageSocket.currentUserId = null\n MessageSocket.currentToken = null\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public static registerCallbacks<T = unknown>(entry: MessageCallbackEntry<T>): void {\n if (!entry) {\n console.warn('[MessageSocket] 注册回调失败,缺少 entry', entry)\n return\n }\n\n if (typeof entry !== 'object') {\n console.warn('[MessageSocket] 注册回调失败,entry 不是对象', entry)\n return\n }\n\n if (typeof entry.callback !== 'function') {\n console.warn('[MessageSocket] 注册回调失败,callback 不是函数', entry)\n return\n }\n\n if (typeof entry.type !== 'string') {\n console.warn('[MessageSocket] 注册回调失败,type 不是字符串', entry)\n return\n }\n\n if (!MessageSocket.client) {\n console.warn('[MessageSocket] WebSocket 客户端未初始化,无法注册回调')\n return\n }\n\n MessageSocket.client.on(entry)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选)\n */\n public static unregisterCallbacks<T = unknown>(type: string, callback?: (data: T, message?: unknown) => void): void {\n if (!MessageSocket.client) {\n return\n }\n\n MessageSocket.client.off(type, callback)\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public static send(data: string | object): void {\n if (!MessageSocket.client) {\n console.warn('[MessageSocket] WebSocket 客户端未初始化,无法发送消息')\n return\n }\n\n MessageSocket.client.send(data)\n }\n\n /**\n * 获取连接状态\n */\n public static getReadyState(): number {\n return MessageSocket.client?.getReadyState() ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public static isConnected(): boolean {\n return MessageSocket.client?.isConnected() ?? false\n }\n\n /**\n * 获取当前用户ID\n */\n public static getCurrentUserId(): string | null {\n return MessageSocket.currentUserId\n }\n\n /**\n * 获取当前token\n */\n public static getCurrentToken(): string | null {\n return MessageSocket.currentToken\n }\n}\n"],"names":["WebSocketClient","constructor","config","this","socket","heartbeatTimer","reconnectTimer","callbackList","currentUrl","reconnectAttempts","manualClose","DEFAULT_CONFIG","updateConfig","connect","url","targetUrl","WebSocket","onopen","console","log","startHeartbeat","onOpen","onmessage","event","handleIncoming","data","onclose","code","reason","stopHeartbeat","onClose","autoReconnect","scheduleReconnect","onerror","error","onError","maxReconnectAttempts","warn","delay","Math","min","reconnectDelay","reconnectDelayMax","window","setTimeout","disconnect","clearTimeout","close","send","readyState","OPEN","message","JSON","stringify","parse","type","filter","entry","forEach","callback","onMessage","setInterval","heartbeatData","heartbeatMessage","heartbeatInterval","clearInterval","on","typeOrEntry","push","off","clearCallbacks","getReadyState","CLOSED","isConnected","_event","_message","timestamp","Date","now","MessageSocket","configure","start","options","userId","token","callbacks","client","currentUserId","currentToken","registerCallbacks","stop","baseUrl","path","clientConfig","encodeURIComponent","unregisterCallbacks","getCurrentUserId","getCurrentToken"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- class e{constructor(t={}){this.socket=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.callbackList=[],this.currentUrl=null,this.reconnectAttempts=0,this.manualClose=!1,this.config={...e.DEFAULT_CONFIG,...t}}updateConfig(e){this.config={...this.config,...e}}connect(e){const t=e||this.config.url;if(t){this.currentUrl=t,this.manualClose=!1;try{this.socket=new WebSocket(t),this.socket.onopen=()=>{console.log("[WebSocketClient] 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),this.onOpen()},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{console.log("[WebSocketClient] 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.onClose(e),!this.manualClose&&this.config.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{console.error("[WebSocketClient] 连接错误",e),this.stopHeartbeat(),this.onError(e)}}catch(e){console.error("[WebSocketClient] 连接失败",e),this.config.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else console.error("[WebSocketClient] 缺少 WebSocket URL")}scheduleReconnect(){if(this.reconnectAttempts>=this.config.maxReconnectAttempts||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=this.config.maxReconnectAttempts&&console.warn("[WebSocketClient] 已达到最大重连次数"));this.reconnectAttempts+=1;const e=Math.min(this.config.reconnectDelay*this.reconnectAttempts,this.config.reconnectDelayMax);console.log(`[WebSocketClient] 将在 ${e}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=window.setTimeout(()=>{this.connect(this.currentUrl)},e)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.currentUrl=null,this.reconnectAttempts=0}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void console.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 console.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){console.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):console.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(){}onClose(e){}onError(e){}onMessage(e){}}e.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,heartbeatMessage:()=>({type:"PING",timestamp:Date.now()})};class t{static configure(e){t.config={...t.config,...e}}static start(n){const{userId:c,token:s,callbacks:o=[]}=n;if(!c||!s)return void console.warn("[MessageSocket] 缺少 userId 或 token,无法启动");if(t.client&&t.client.isConnected()&&t.currentUserId===c&&t.currentToken===s)return console.log("[MessageSocket] 复用现有连接"),void o.forEach(e=>t.registerCallbacks(e));t.stop(),t.currentUserId=c,t.currentToken=s;const{baseUrl:r,path:i,...a}=t.config,l=`${r}${i}/${c}?token=${encodeURIComponent(s)}`;t.client=new e({...a,url:l}),o.forEach(e=>t.registerCallbacks(e)),t.client.connect()}static stop(){t.client&&(t.client.disconnect(),t.client.clearCallbacks(),t.client=null),t.currentUserId=null,t.currentToken=null}static registerCallbacks(e){e?"object"==typeof e?"function"==typeof e.callback?"string"==typeof e.type?t.client?t.client.on(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法注册回调"):console.warn("[MessageSocket] 注册回调失败,type 不是字符串",e):console.warn("[MessageSocket] 注册回调失败,callback 不是函数",e):console.warn("[MessageSocket] 注册回调失败,entry 不是对象",e):console.warn("[MessageSocket] 注册回调失败,缺少 entry",e)}static unregisterCallbacks(e,n){t.client&&t.client.off(e,n)}static send(e){t.client?t.client.send(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法发送消息")}static getReadyState(){return t.client?.getReadyState()??WebSocket.CLOSED}static isConnected(){return t.client?.isConnected()??!1}static getCurrentUserId(){return t.currentUserId}static getCurrentToken(){return t.currentToken}}t.DEFAULT_CONFIG={baseUrl:"ws://dev-gateway.chinamarket.cn",path:"/api/user-web/websocket/messageServer",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0},t.client=null,t.currentUserId=null,t.currentToken=null,t.config={...t.DEFAULT_CONFIG};export{t as MessageSocket};
1
+ class e{constructor(t={}){this.socket=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.callbackList=[],this.currentUrl=null,this.reconnectAttempts=0,this.manualClose=!1,this.config={...e.DEFAULT_CONFIG,...t}}updateConfig(e){this.config={...this.config,...e}}connect(e){const t=e||this.config.url;if(t){this.currentUrl=t,this.manualClose=!1;try{this.socket=new WebSocket(t),this.socket.onopen=()=>{console.log("[WebSocketClient] 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),this.onOpen()},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{console.log("[WebSocketClient] 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.onClose(e),!this.manualClose&&this.config.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{console.error("[WebSocketClient] 连接错误",e),this.stopHeartbeat(),this.onError(e)}}catch(e){console.error("[WebSocketClient] 连接失败",e),this.config.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else console.error("[WebSocketClient] 缺少 WebSocket URL")}scheduleReconnect(){if(this.reconnectAttempts>=this.config.maxReconnectAttempts||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=this.config.maxReconnectAttempts&&console.warn("[WebSocketClient] 已达到最大重连次数"));this.reconnectAttempts+=1;const e=Math.min(this.config.reconnectDelay*this.reconnectAttempts,this.config.reconnectDelayMax);console.log(`[WebSocketClient] 将在 ${e}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=window.setTimeout(()=>{this.connect(this.currentUrl)},e)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.currentUrl=null,this.reconnectAttempts=0}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void console.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 console.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){console.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):console.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(){}onClose(e){}onError(e){}onMessage(e){}}e.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,heartbeatMessage:()=>({type:"PING",timestamp:Date.now()})};class t{static configure(e){t.config={...t.config,...e}}static start(n){const{userId:c,token:s,callbacks:o=[]}=n;if(!c||!s)return void console.warn("[MessageSocket] 缺少 userId 或 token,无法启动");if(t.client&&t.client.isConnected()&&t.currentUserId===c&&t.currentToken===s)return console.log("[MessageSocket] 复用现有连接"),void o.forEach(e=>t.registerCallbacks(e));t.stop(),t.currentUserId=c,t.currentToken=s;const{baseUrl:r,path:i,...a}=t.config,l=`${r}${i}/${c}?token=${encodeURIComponent(s)}`;t.client=new e({...a,url:l}),o.forEach(e=>t.registerCallbacks(e)),t.client.connect()}static stop(){t.client&&(t.client.disconnect(),t.client.clearCallbacks(),t.client=null),t.currentUserId=null,t.currentToken=null}static registerCallbacks(e){e?"object"==typeof e?"function"==typeof e.callback?"string"==typeof e.type?t.client?t.client.on(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法注册回调"):console.warn("[MessageSocket] 注册回调失败,type 不是字符串",e):console.warn("[MessageSocket] 注册回调失败,callback 不是函数",e):console.warn("[MessageSocket] 注册回调失败,entry 不是对象",e):console.warn("[MessageSocket] 注册回调失败,缺少 entry",e)}static unregisterCallbacks(e,n){t.client&&t.client.off(e,n)}static send(e){t.client?t.client.send(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法发送消息")}static getReadyState(){return t.client?.getReadyState()??WebSocket.CLOSED}static isConnected(){return t.client?.isConnected()??!1}static getCurrentUserId(){return t.currentUserId}static getCurrentToken(){return t.currentToken}}t.DEFAULT_CONFIG={baseUrl:"wss://dev-gateway.chinamarket.cn",path:"/api/user-web/websocket/messageServer",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0},t.client=null,t.currentUserId=null,t.currentToken=null,t.config={...t.DEFAULT_CONFIG};export{t as MessageSocket};
2
2
  //# sourceMappingURL=MessageSocket.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageSocket.esm.js","sources":["../src/WebSocketClient.ts","../src/MessageSocket.ts"],"sourcesContent":["/**\n * WebSocket 配置选项\n */\nexport interface WebSocketConfig {\n /** WebSocket 服务器地址(可选,可以在 connect 时指定) */\n url?: string\n /** 心跳间隔(毫秒),默认 25000 */\n heartbeatInterval?: number\n /** 最大重连次数,默认 10 */\n maxReconnectAttempts?: number\n /** 重连延迟(毫秒),默认 3000 */\n reconnectDelay?: number\n /** 最大重连延迟(毫秒),默认 10000 */\n reconnectDelayMax?: number\n /** 心跳消息生成器 */\n heartbeatMessage?: () => string | object\n /** 是否自动重连,默认 true */\n autoReconnect?: boolean\n}\n\n/**\n * 消息数据结构\n */\nexport interface MessageData<T = unknown> {\n /** 消息类型 */\n type: string\n /** 消息数据 */\n data: T\n /** 元数据 */\n meta?: Record<string, unknown>\n /** 时间戳 */\n timestamp?: number\n}\n\n/**\n * 消息回调函数\n */\nexport type MessageCallback<T = unknown> = (data: T, message?: MessageData<T>) => void\n\n/**\n * 消息回调配置\n */\nexport interface MessageCallbackEntry<T = unknown> {\n /** 消息类型 */\n type: string\n /** 回调函数 */\n callback: MessageCallback<T>\n}\n\n/**\n * WebSocket 基础封装类\n * 提供连接管理、心跳、自动重连、消息回调等基础功能\n */\nexport class WebSocketClient {\n /** WebSocket 连接实例 */\n protected socket: WebSocket | null = null\n\n /** 心跳定时器 */\n protected heartbeatTimer: number | null = null\n\n /** 重连定时器 */\n protected reconnectTimer: number | null = null\n\n /** 消息回调列表 */\n protected callbackList: MessageCallbackEntry<unknown>[] = []\n\n /** 当前连接的 URL */\n protected currentUrl: string | null = null\n\n /** 配置选项 */\n protected config: Required<WebSocketConfig>\n\n /** 重连次数 */\n protected reconnectAttempts = 0\n\n /** 是否手动关闭 */\n protected manualClose = false\n\n /**\n * 默认配置\n */\n protected static readonly DEFAULT_CONFIG: Required<WebSocketConfig> = {\n url: '',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n heartbeatMessage: () => ({\n type: 'PING',\n timestamp: Date.now(),\n }),\n }\n\n /**\n * 构造函数\n * @param config 配置选项\n */\n constructor(config: WebSocketConfig = {}) {\n this.config = { ...WebSocketClient.DEFAULT_CONFIG, ...config }\n }\n\n /**\n * 更新配置\n * @param config 新的配置选项\n */\n public updateConfig(config: Partial<WebSocketConfig>): void {\n this.config = { ...this.config, ...config }\n }\n\n /**\n * 连接到 WebSocket 服务器\n * @param url WebSocket 地址(可选,如果不传则使用配置中的 url)\n */\n public connect(url?: string): void {\n const targetUrl = url || this.config.url\n if (!targetUrl) {\n console.error('[WebSocketClient] 缺少 WebSocket URL')\n return\n }\n\n this.currentUrl = targetUrl\n this.manualClose = false\n\n try {\n this.socket = new WebSocket(targetUrl)\n\n this.socket.onopen = () => {\n console.log('[WebSocketClient] 连接成功')\n this.reconnectAttempts = 0\n this.startHeartbeat()\n this.onOpen()\n }\n\n this.socket.onmessage = (event: MessageEvent) => {\n this.handleIncoming(event.data)\n }\n\n this.socket.onclose = (event: CloseEvent) => {\n console.log('[WebSocketClient] 连接关闭', event.code, event.reason)\n this.stopHeartbeat()\n this.onClose(event)\n\n if (!this.manualClose && this.config.autoReconnect) {\n this.scheduleReconnect()\n }\n }\n\n this.socket.onerror = (event: Event) => {\n console.error('[WebSocketClient] 连接错误', event)\n this.stopHeartbeat()\n this.onError(event)\n }\n } catch (error) {\n console.error('[WebSocketClient] 连接失败', error)\n if (this.config.autoReconnect && !this.manualClose) {\n this.scheduleReconnect()\n }\n }\n }\n\n /**\n * 计划重连\n */\n protected scheduleReconnect(): void {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {\n console.warn('[WebSocketClient] 已达到最大重连次数')\n }\n return\n }\n\n this.reconnectAttempts += 1\n const delay = Math.min(this.config.reconnectDelay * this.reconnectAttempts, this.config.reconnectDelayMax)\n\n console.log(`[WebSocketClient] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`)\n\n this.reconnectTimer = window.setTimeout(() => {\n this.connect(this.currentUrl!)\n }, delay)\n }\n\n /**\n * 断开连接\n */\n public disconnect(): void {\n this.manualClose = true\n this.stopHeartbeat()\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n\n if (this.socket) {\n this.socket.close()\n this.socket = null\n }\n\n this.currentUrl = null\n this.reconnectAttempts = 0\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public send(data: string | object): void {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n console.warn('[WebSocketClient] WebSocket 未连接,无法发送消息')\n return\n }\n\n const message = typeof data === 'string' ? data : JSON.stringify(data)\n this.socket.send(message)\n }\n\n /**\n * 处理接收的消息\n * @param data 接收到的数据\n */\n protected handleIncoming(data: string): void {\n if (!data) return\n\n let message: MessageData\n try {\n message = JSON.parse(data)\n } catch {\n console.warn('[WebSocketClient] 无法解析消息', data)\n return\n }\n\n if (!message?.type) {\n return\n }\n\n // 触发匹配的回调\n const matched = this.callbackList.filter((entry) => entry.type === message.type)\n matched.forEach(({ callback }) => {\n try {\n callback(message.data, message)\n } catch (error) {\n console.error('[WebSocketClient] 回调执行失败', error)\n }\n })\n\n // 调用钩子\n this.onMessage(message)\n }\n\n /**\n * 启动心跳\n */\n protected startHeartbeat(): void {\n this.stopHeartbeat()\n\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n this.heartbeatTimer = window.setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n const heartbeatData = this.config.heartbeatMessage()\n this.send(heartbeatData)\n }, this.config.heartbeatInterval)\n }\n\n /**\n * 停止心跳\n */\n protected stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public on<T = unknown>(type: string, callback: MessageCallback<T>): void\n public on<T = unknown>(entry: MessageCallbackEntry<T>): void\n public on<T = unknown>(typeOrEntry: string | MessageCallbackEntry<T>, callback?: MessageCallback<T>): void {\n const entry: MessageCallbackEntry<T> =\n typeof typeOrEntry === 'string' ? { type: typeOrEntry, callback: callback! } : typeOrEntry\n\n if (!entry.type || typeof entry.callback !== 'function') {\n console.warn('[WebSocketClient] 无效的回调配置', entry)\n return\n }\n\n this.callbackList.push(entry as MessageCallbackEntry<unknown>)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选,如果不传则移除该类型的所有回调)\n */\n public off<T = unknown>(type: string, callback?: MessageCallback<T>): void {\n if (callback) {\n this.callbackList = this.callbackList.filter((entry) => !(entry.type === type && entry.callback === callback))\n } else {\n this.callbackList = this.callbackList.filter((entry) => entry.type !== type)\n }\n }\n\n /**\n * 清空所有回调\n */\n public clearCallbacks(): void {\n this.callbackList = []\n }\n\n /**\n * 获取当前连接状态\n */\n public getReadyState(): number {\n return this.socket?.readyState ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public isConnected(): boolean {\n return this.socket?.readyState === WebSocket.OPEN\n }\n\n // ========== 钩子方法(子类可以重写) ==========\n\n /**\n * 连接打开时的钩子\n */\n protected onOpen(): void {\n // 子类可以重写\n }\n\n /**\n * 连接关闭时的钩子\n */\n protected onClose(_event: CloseEvent): void {\n // 子类可以重写\n }\n\n /**\n * 连接错误时的钩子\n */\n protected onError(_event: Event): void {\n // 子类可以重写\n }\n\n /**\n * 收到消息时的钩子\n */\n protected onMessage(_message: MessageData): void {\n // 子类可以重写\n }\n}\n","import { WebSocketClient, WebSocketConfig, MessageCallbackEntry } from './WebSocketClient'\n\n/**\n * MessageSocket 配置选项\n */\nexport interface MessageSocketConfig extends WebSocketConfig {\n /** WebSocket 服务器基础地址,默认 'ws://dev-gateway.chinamarket.cn' */\n baseUrl?: string\n /** WebSocket 路径,默认 '/api/user-web/websocket/messageServer' */\n path?: string\n}\n\n/**\n * MessageSocket 启动选项\n */\nexport interface MessageSocketStartOptions {\n /** 用户ID(必需) */\n userId: string\n /** 认证令牌(必需) */\n token: string\n /** 消息回调列表 */\n callbacks?: MessageCallbackEntry[]\n}\n\n/**\n * 消息 Socket 类\n * 基于 WebSocketClient,提供用户认证和消息管理功能\n * 用于获取用户未读消息数量等业务场景\n *\n * @example\n * ```typescript\n * // 启动连接\n * MessageSocket.start({\n * userId: '1234567890',\n * token: 'your-token',\n * callbacks: [\n * {\n * type: 'UNREAD_COUNT',\n * callback: (payload) => {\n * console.log('未读消息数:', payload)\n * }\n * }\n * ]\n * })\n *\n * // 停止连接\n * MessageSocket.stop()\n *\n * // 动态注册回调\n * MessageSocket.registerCallbacks({\n * type: 'NEW_MESSAGE',\n * callback: (payload) => {\n * console.log('新消息:', payload)\n * }\n * })\n * ```\n */\nexport class MessageSocket {\n /** 默认配置 */\n private static readonly DEFAULT_CONFIG: MessageSocketConfig = {\n baseUrl: 'ws://dev-gateway.chinamarket.cn',\n path: '/api/user-web/websocket/messageServer',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n }\n\n /** WebSocket 客户端实例 */\n private static client: WebSocketClient | null = null\n\n /** 当前用户ID */\n private static currentUserId: string | null = null\n\n /** 当前token */\n private static currentToken: string | null = null\n\n /** 当前配置 */\n private static config: MessageSocketConfig = { ...MessageSocket.DEFAULT_CONFIG }\n\n /**\n * 配置 MessageSocket\n * @param config 配置选项\n */\n public static configure(config: Partial<MessageSocketConfig>): void {\n MessageSocket.config = { ...MessageSocket.config, ...config }\n }\n\n /**\n * 启动连接\n * @param options 启动选项\n */\n public static start(options: MessageSocketStartOptions): void {\n const { userId, token, callbacks = [] } = options\n\n if (!userId || !token) {\n console.warn('[MessageSocket] 缺少 userId 或 token,无法启动')\n return\n }\n\n // 检查是否可以复用现有连接\n const shouldReuse =\n MessageSocket.client &&\n MessageSocket.client.isConnected() &&\n MessageSocket.currentUserId === userId &&\n MessageSocket.currentToken === token\n\n if (shouldReuse) {\n console.log('[MessageSocket] 复用现有连接')\n // 重新注册回调\n callbacks.forEach((entry) => MessageSocket.registerCallbacks(entry))\n return\n }\n\n // 停止旧连接\n MessageSocket.stop()\n\n // 保存当前用户信息\n MessageSocket.currentUserId = userId\n MessageSocket.currentToken = token\n\n // 构建 WebSocket URL\n const { baseUrl, path, ...clientConfig } = MessageSocket.config\n const url = `${baseUrl}${path}/${userId}?token=${encodeURIComponent(token)}`\n\n // 创建新的 WebSocket 客户端\n MessageSocket.client = new WebSocketClient({\n ...clientConfig,\n url,\n })\n\n // 注册回调\n callbacks.forEach((entry) => MessageSocket.registerCallbacks(entry))\n\n // 连接\n MessageSocket.client.connect()\n }\n\n /**\n * 停止连接\n */\n public static stop(): void {\n if (MessageSocket.client) {\n MessageSocket.client.disconnect()\n MessageSocket.client.clearCallbacks()\n MessageSocket.client = null\n }\n\n MessageSocket.currentUserId = null\n MessageSocket.currentToken = null\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public static registerCallbacks<T = unknown>(entry: MessageCallbackEntry<T>): void {\n if (!entry) {\n console.warn('[MessageSocket] 注册回调失败,缺少 entry', entry)\n return\n }\n\n if (typeof entry !== 'object') {\n console.warn('[MessageSocket] 注册回调失败,entry 不是对象', entry)\n return\n }\n\n if (typeof entry.callback !== 'function') {\n console.warn('[MessageSocket] 注册回调失败,callback 不是函数', entry)\n return\n }\n\n if (typeof entry.type !== 'string') {\n console.warn('[MessageSocket] 注册回调失败,type 不是字符串', entry)\n return\n }\n\n if (!MessageSocket.client) {\n console.warn('[MessageSocket] WebSocket 客户端未初始化,无法注册回调')\n return\n }\n\n MessageSocket.client.on(entry)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选)\n */\n public static unregisterCallbacks<T = unknown>(type: string, callback?: (data: T, message?: unknown) => void): void {\n if (!MessageSocket.client) {\n return\n }\n\n MessageSocket.client.off(type, callback)\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public static send(data: string | object): void {\n if (!MessageSocket.client) {\n console.warn('[MessageSocket] WebSocket 客户端未初始化,无法发送消息')\n return\n }\n\n MessageSocket.client.send(data)\n }\n\n /**\n * 获取连接状态\n */\n public static getReadyState(): number {\n return MessageSocket.client?.getReadyState() ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public static isConnected(): boolean {\n return MessageSocket.client?.isConnected() ?? false\n }\n\n /**\n * 获取当前用户ID\n */\n public static getCurrentUserId(): string | null {\n return MessageSocket.currentUserId\n }\n\n /**\n * 获取当前token\n */\n public static getCurrentToken(): string | null {\n return MessageSocket.currentToken\n }\n}\n"],"names":["WebSocketClient","constructor","config","this","socket","heartbeatTimer","reconnectTimer","callbackList","currentUrl","reconnectAttempts","manualClose","DEFAULT_CONFIG","updateConfig","connect","url","targetUrl","WebSocket","onopen","console","log","startHeartbeat","onOpen","onmessage","event","handleIncoming","data","onclose","code","reason","stopHeartbeat","onClose","autoReconnect","scheduleReconnect","onerror","error","onError","maxReconnectAttempts","warn","delay","Math","min","reconnectDelay","reconnectDelayMax","window","setTimeout","disconnect","clearTimeout","close","send","readyState","OPEN","message","JSON","stringify","parse","type","filter","entry","forEach","callback","onMessage","setInterval","heartbeatData","heartbeatMessage","heartbeatInterval","clearInterval","on","typeOrEntry","push","off","clearCallbacks","getReadyState","CLOSED","isConnected","_event","_message","timestamp","Date","now","MessageSocket","configure","start","options","userId","token","callbacks","client","currentUserId","currentToken","registerCallbacks","stop","baseUrl","path","clientConfig","encodeURIComponent","unregisterCallbacks","getCurrentUserId","getCurrentToken"],"mappings":"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"}
1
+ {"version":3,"file":"MessageSocket.esm.js","sources":["../src/WebSocketClient.ts","../src/MessageSocket.ts"],"sourcesContent":["/**\n * WebSocket 配置选项\n */\nexport interface WebSocketConfig {\n /** WebSocket 服务器地址(可选,可以在 connect 时指定) */\n url?: string\n /** 心跳间隔(毫秒),默认 25000 */\n heartbeatInterval?: number\n /** 最大重连次数,默认 10 */\n maxReconnectAttempts?: number\n /** 重连延迟(毫秒),默认 3000 */\n reconnectDelay?: number\n /** 最大重连延迟(毫秒),默认 10000 */\n reconnectDelayMax?: number\n /** 心跳消息生成器 */\n heartbeatMessage?: () => string | object\n /** 是否自动重连,默认 true */\n autoReconnect?: boolean\n}\n\n/**\n * 消息数据结构\n */\nexport interface MessageData<T = unknown> {\n /** 消息类型 */\n type: string\n /** 消息数据 */\n data: T\n /** 元数据 */\n meta?: Record<string, unknown>\n /** 时间戳 */\n timestamp?: number\n}\n\n/**\n * 消息回调函数\n */\nexport type MessageCallback<T = unknown> = (data: T, message?: MessageData<T>) => void\n\n/**\n * 消息回调配置\n */\nexport interface MessageCallbackEntry<T = unknown> {\n /** 消息类型 */\n type: string\n /** 回调函数 */\n callback: MessageCallback<T>\n}\n\n/**\n * WebSocket 基础封装类\n * 提供连接管理、心跳、自动重连、消息回调等基础功能\n */\nexport class WebSocketClient {\n /** WebSocket 连接实例 */\n protected socket: WebSocket | null = null\n\n /** 心跳定时器 */\n protected heartbeatTimer: number | null = null\n\n /** 重连定时器 */\n protected reconnectTimer: number | null = null\n\n /** 消息回调列表 */\n protected callbackList: MessageCallbackEntry<unknown>[] = []\n\n /** 当前连接的 URL */\n protected currentUrl: string | null = null\n\n /** 配置选项 */\n protected config: Required<WebSocketConfig>\n\n /** 重连次数 */\n protected reconnectAttempts = 0\n\n /** 是否手动关闭 */\n protected manualClose = false\n\n /**\n * 默认配置\n */\n protected static readonly DEFAULT_CONFIG: Required<WebSocketConfig> = {\n url: '',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n heartbeatMessage: () => ({\n type: 'PING',\n timestamp: Date.now(),\n }),\n }\n\n /**\n * 构造函数\n * @param config 配置选项\n */\n constructor(config: WebSocketConfig = {}) {\n this.config = { ...WebSocketClient.DEFAULT_CONFIG, ...config }\n }\n\n /**\n * 更新配置\n * @param config 新的配置选项\n */\n public updateConfig(config: Partial<WebSocketConfig>): void {\n this.config = { ...this.config, ...config }\n }\n\n /**\n * 连接到 WebSocket 服务器\n * @param url WebSocket 地址(可选,如果不传则使用配置中的 url)\n */\n public connect(url?: string): void {\n const targetUrl = url || this.config.url\n if (!targetUrl) {\n console.error('[WebSocketClient] 缺少 WebSocket URL')\n return\n }\n\n this.currentUrl = targetUrl\n this.manualClose = false\n\n try {\n this.socket = new WebSocket(targetUrl)\n\n this.socket.onopen = () => {\n console.log('[WebSocketClient] 连接成功')\n this.reconnectAttempts = 0\n this.startHeartbeat()\n this.onOpen()\n }\n\n this.socket.onmessage = (event: MessageEvent) => {\n this.handleIncoming(event.data)\n }\n\n this.socket.onclose = (event: CloseEvent) => {\n console.log('[WebSocketClient] 连接关闭', event.code, event.reason)\n this.stopHeartbeat()\n this.onClose(event)\n\n if (!this.manualClose && this.config.autoReconnect) {\n this.scheduleReconnect()\n }\n }\n\n this.socket.onerror = (event: Event) => {\n console.error('[WebSocketClient] 连接错误', event)\n this.stopHeartbeat()\n this.onError(event)\n }\n } catch (error) {\n console.error('[WebSocketClient] 连接失败', error)\n if (this.config.autoReconnect && !this.manualClose) {\n this.scheduleReconnect()\n }\n }\n }\n\n /**\n * 计划重连\n */\n protected scheduleReconnect(): void {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {\n console.warn('[WebSocketClient] 已达到最大重连次数')\n }\n return\n }\n\n this.reconnectAttempts += 1\n const delay = Math.min(this.config.reconnectDelay * this.reconnectAttempts, this.config.reconnectDelayMax)\n\n console.log(`[WebSocketClient] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`)\n\n this.reconnectTimer = window.setTimeout(() => {\n this.connect(this.currentUrl!)\n }, delay)\n }\n\n /**\n * 断开连接\n */\n public disconnect(): void {\n this.manualClose = true\n this.stopHeartbeat()\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n\n if (this.socket) {\n this.socket.close()\n this.socket = null\n }\n\n this.currentUrl = null\n this.reconnectAttempts = 0\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public send(data: string | object): void {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n console.warn('[WebSocketClient] WebSocket 未连接,无法发送消息')\n return\n }\n\n const message = typeof data === 'string' ? data : JSON.stringify(data)\n this.socket.send(message)\n }\n\n /**\n * 处理接收的消息\n * @param data 接收到的数据\n */\n protected handleIncoming(data: string): void {\n if (!data) return\n\n let message: MessageData\n try {\n message = JSON.parse(data)\n } catch {\n console.warn('[WebSocketClient] 无法解析消息', data)\n return\n }\n\n if (!message?.type) {\n return\n }\n\n // 触发匹配的回调\n const matched = this.callbackList.filter((entry) => entry.type === message.type)\n matched.forEach(({ callback }) => {\n try {\n callback(message.data, message)\n } catch (error) {\n console.error('[WebSocketClient] 回调执行失败', error)\n }\n })\n\n // 调用钩子\n this.onMessage(message)\n }\n\n /**\n * 启动心跳\n */\n protected startHeartbeat(): void {\n this.stopHeartbeat()\n\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n this.heartbeatTimer = window.setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n const heartbeatData = this.config.heartbeatMessage()\n this.send(heartbeatData)\n }, this.config.heartbeatInterval)\n }\n\n /**\n * 停止心跳\n */\n protected stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public on<T = unknown>(type: string, callback: MessageCallback<T>): void\n public on<T = unknown>(entry: MessageCallbackEntry<T>): void\n public on<T = unknown>(typeOrEntry: string | MessageCallbackEntry<T>, callback?: MessageCallback<T>): void {\n const entry: MessageCallbackEntry<T> =\n typeof typeOrEntry === 'string' ? { type: typeOrEntry, callback: callback! } : typeOrEntry\n\n if (!entry.type || typeof entry.callback !== 'function') {\n console.warn('[WebSocketClient] 无效的回调配置', entry)\n return\n }\n\n this.callbackList.push(entry as MessageCallbackEntry<unknown>)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选,如果不传则移除该类型的所有回调)\n */\n public off<T = unknown>(type: string, callback?: MessageCallback<T>): void {\n if (callback) {\n this.callbackList = this.callbackList.filter((entry) => !(entry.type === type && entry.callback === callback))\n } else {\n this.callbackList = this.callbackList.filter((entry) => entry.type !== type)\n }\n }\n\n /**\n * 清空所有回调\n */\n public clearCallbacks(): void {\n this.callbackList = []\n }\n\n /**\n * 获取当前连接状态\n */\n public getReadyState(): number {\n return this.socket?.readyState ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public isConnected(): boolean {\n return this.socket?.readyState === WebSocket.OPEN\n }\n\n // ========== 钩子方法(子类可以重写) ==========\n\n /**\n * 连接打开时的钩子\n */\n protected onOpen(): void {\n // 子类可以重写\n }\n\n /**\n * 连接关闭时的钩子\n */\n protected onClose(_event: CloseEvent): void {\n // 子类可以重写\n }\n\n /**\n * 连接错误时的钩子\n */\n protected onError(_event: Event): void {\n // 子类可以重写\n }\n\n /**\n * 收到消息时的钩子\n */\n protected onMessage(_message: MessageData): void {\n // 子类可以重写\n }\n}\n","import { WebSocketClient, WebSocketConfig, MessageCallbackEntry } from './WebSocketClient'\n\n/**\n * MessageSocket 配置选项\n */\nexport interface MessageSocketConfig extends WebSocketConfig {\n /** WebSocket 服务器基础地址,默认 'ws://dev-gateway.chinamarket.cn' */\n baseUrl?: string\n /** WebSocket 路径,默认 '/api/user-web/websocket/messageServer' */\n path?: string\n}\n\n/**\n * MessageSocket 启动选项\n */\nexport interface MessageSocketStartOptions {\n /** 用户ID(必需) */\n userId: string\n /** 认证令牌(必需) */\n token: string\n /** 消息回调列表 */\n callbacks?: MessageCallbackEntry[]\n}\n\n/**\n * 消息 Socket 类\n * 基于 WebSocketClient,提供用户认证和消息管理功能\n * 用于获取用户未读消息数量等业务场景\n *\n * @example\n * ```typescript\n * // 启动连接\n * MessageSocket.start({\n * userId: '1234567890',\n * token: 'your-token',\n * callbacks: [\n * {\n * type: 'UNREAD_COUNT',\n * callback: (payload) => {\n * console.log('未读消息数:', payload)\n * }\n * }\n * ]\n * })\n *\n * // 停止连接\n * MessageSocket.stop()\n *\n * // 动态注册回调\n * MessageSocket.registerCallbacks({\n * type: 'NEW_MESSAGE',\n * callback: (payload) => {\n * console.log('新消息:', payload)\n * }\n * })\n * ```\n */\nexport class MessageSocket {\n /** 默认配置 */\n private static readonly DEFAULT_CONFIG: MessageSocketConfig = {\n baseUrl: 'wss://dev-gateway.chinamarket.cn',\n path: '/api/user-web/websocket/messageServer',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n }\n\n /** WebSocket 客户端实例 */\n private static client: WebSocketClient | null = null\n\n /** 当前用户ID */\n private static currentUserId: string | null = null\n\n /** 当前token */\n private static currentToken: string | null = null\n\n /** 当前配置 */\n private static config: MessageSocketConfig = { ...MessageSocket.DEFAULT_CONFIG }\n\n /**\n * 配置 MessageSocket\n * @param config 配置选项\n */\n public static configure(config: Partial<MessageSocketConfig>): void {\n MessageSocket.config = { ...MessageSocket.config, ...config }\n }\n\n /**\n * 启动连接\n * @param options 启动选项\n */\n public static start(options: MessageSocketStartOptions): void {\n const { userId, token, callbacks = [] } = options\n\n if (!userId || !token) {\n console.warn('[MessageSocket] 缺少 userId 或 token,无法启动')\n return\n }\n\n // 检查是否可以复用现有连接\n const shouldReuse =\n MessageSocket.client &&\n MessageSocket.client.isConnected() &&\n MessageSocket.currentUserId === userId &&\n MessageSocket.currentToken === token\n\n if (shouldReuse) {\n console.log('[MessageSocket] 复用现有连接')\n // 重新注册回调\n callbacks.forEach((entry) => MessageSocket.registerCallbacks(entry))\n return\n }\n\n // 停止旧连接\n MessageSocket.stop()\n\n // 保存当前用户信息\n MessageSocket.currentUserId = userId\n MessageSocket.currentToken = token\n\n // 构建 WebSocket URL\n const { baseUrl, path, ...clientConfig } = MessageSocket.config\n const url = `${baseUrl}${path}/${userId}?token=${encodeURIComponent(token)}`\n\n // 创建新的 WebSocket 客户端\n MessageSocket.client = new WebSocketClient({\n ...clientConfig,\n url,\n })\n\n // 注册回调\n callbacks.forEach((entry) => MessageSocket.registerCallbacks(entry))\n\n // 连接\n MessageSocket.client.connect()\n }\n\n /**\n * 停止连接\n */\n public static stop(): void {\n if (MessageSocket.client) {\n MessageSocket.client.disconnect()\n MessageSocket.client.clearCallbacks()\n MessageSocket.client = null\n }\n\n MessageSocket.currentUserId = null\n MessageSocket.currentToken = null\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public static registerCallbacks<T = unknown>(entry: MessageCallbackEntry<T>): void {\n if (!entry) {\n console.warn('[MessageSocket] 注册回调失败,缺少 entry', entry)\n return\n }\n\n if (typeof entry !== 'object') {\n console.warn('[MessageSocket] 注册回调失败,entry 不是对象', entry)\n return\n }\n\n if (typeof entry.callback !== 'function') {\n console.warn('[MessageSocket] 注册回调失败,callback 不是函数', entry)\n return\n }\n\n if (typeof entry.type !== 'string') {\n console.warn('[MessageSocket] 注册回调失败,type 不是字符串', entry)\n return\n }\n\n if (!MessageSocket.client) {\n console.warn('[MessageSocket] WebSocket 客户端未初始化,无法注册回调')\n return\n }\n\n MessageSocket.client.on(entry)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选)\n */\n public static unregisterCallbacks<T = unknown>(type: string, callback?: (data: T, message?: unknown) => void): void {\n if (!MessageSocket.client) {\n return\n }\n\n MessageSocket.client.off(type, callback)\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public static send(data: string | object): void {\n if (!MessageSocket.client) {\n console.warn('[MessageSocket] WebSocket 客户端未初始化,无法发送消息')\n return\n }\n\n MessageSocket.client.send(data)\n }\n\n /**\n * 获取连接状态\n */\n public static getReadyState(): number {\n return MessageSocket.client?.getReadyState() ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public static isConnected(): boolean {\n return MessageSocket.client?.isConnected() ?? false\n }\n\n /**\n * 获取当前用户ID\n */\n public static getCurrentUserId(): string | null {\n return MessageSocket.currentUserId\n }\n\n /**\n * 获取当前token\n */\n public static getCurrentToken(): string | null {\n return MessageSocket.currentToken\n }\n}\n"],"names":["WebSocketClient","constructor","config","this","socket","heartbeatTimer","reconnectTimer","callbackList","currentUrl","reconnectAttempts","manualClose","DEFAULT_CONFIG","updateConfig","connect","url","targetUrl","WebSocket","onopen","console","log","startHeartbeat","onOpen","onmessage","event","handleIncoming","data","onclose","code","reason","stopHeartbeat","onClose","autoReconnect","scheduleReconnect","onerror","error","onError","maxReconnectAttempts","warn","delay","Math","min","reconnectDelay","reconnectDelayMax","window","setTimeout","disconnect","clearTimeout","close","send","readyState","OPEN","message","JSON","stringify","parse","type","filter","entry","forEach","callback","onMessage","setInterval","heartbeatData","heartbeatMessage","heartbeatInterval","clearInterval","on","typeOrEntry","push","off","clearCallbacks","getReadyState","CLOSED","isConnected","_event","_message","timestamp","Date","now","MessageSocket","configure","start","options","userId","token","callbacks","client","currentUserId","currentToken","registerCallbacks","stop","baseUrl","path","clientConfig","encodeURIComponent","unregisterCallbacks","getCurrentUserId","getCurrentToken"],"mappings":"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"}
package/dist/index.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";class e{constructor(t={}){this.socket=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.callbackList=[],this.currentUrl=null,this.reconnectAttempts=0,this.manualClose=!1,this.config={...e.DEFAULT_CONFIG,...t}}updateConfig(e){this.config={...this.config,...e}}connect(e){const t=e||this.config.url;if(t){this.currentUrl=t,this.manualClose=!1;try{this.socket=new WebSocket(t),this.socket.onopen=()=>{console.log("[WebSocketClient] 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),this.onOpen()},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{console.log("[WebSocketClient] 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.onClose(e),!this.manualClose&&this.config.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{console.error("[WebSocketClient] 连接错误",e),this.stopHeartbeat(),this.onError(e)}}catch(e){console.error("[WebSocketClient] 连接失败",e),this.config.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else console.error("[WebSocketClient] 缺少 WebSocket URL")}scheduleReconnect(){if(this.reconnectAttempts>=this.config.maxReconnectAttempts||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=this.config.maxReconnectAttempts&&console.warn("[WebSocketClient] 已达到最大重连次数"));this.reconnectAttempts+=1;const e=Math.min(this.config.reconnectDelay*this.reconnectAttempts,this.config.reconnectDelayMax);console.log(`[WebSocketClient] 将在 ${e}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=window.setTimeout(()=>{this.connect(this.currentUrl)},e)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.currentUrl=null,this.reconnectAttempts=0}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void console.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 console.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){console.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):console.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(){}onClose(e){}onError(e){}onMessage(e){}}e.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,heartbeatMessage:()=>({type:"PING",timestamp:Date.now()})};class t{static configure(e){t.config={...t.config,...e}}static start(n){const{userId:c,token:s,callbacks:o=[]}=n;if(!c||!s)return void console.warn("[MessageSocket] 缺少 userId 或 token,无法启动");if(t.client&&t.client.isConnected()&&t.currentUserId===c&&t.currentToken===s)return console.log("[MessageSocket] 复用现有连接"),void o.forEach(e=>t.registerCallbacks(e));t.stop(),t.currentUserId=c,t.currentToken=s;const{baseUrl:r,path:i,...a}=t.config,l=`${r}${i}/${c}?token=${encodeURIComponent(s)}`;t.client=new e({...a,url:l}),o.forEach(e=>t.registerCallbacks(e)),t.client.connect()}static stop(){t.client&&(t.client.disconnect(),t.client.clearCallbacks(),t.client=null),t.currentUserId=null,t.currentToken=null}static registerCallbacks(e){e?"object"==typeof e?"function"==typeof e.callback?"string"==typeof e.type?t.client?t.client.on(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法注册回调"):console.warn("[MessageSocket] 注册回调失败,type 不是字符串",e):console.warn("[MessageSocket] 注册回调失败,callback 不是函数",e):console.warn("[MessageSocket] 注册回调失败,entry 不是对象",e):console.warn("[MessageSocket] 注册回调失败,缺少 entry",e)}static unregisterCallbacks(e,n){t.client&&t.client.off(e,n)}static send(e){t.client?t.client.send(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法发送消息")}static getReadyState(){return t.client?.getReadyState()??WebSocket.CLOSED}static isConnected(){return t.client?.isConnected()??!1}static getCurrentUserId(){return t.currentUserId}static getCurrentToken(){return t.currentToken}}t.DEFAULT_CONFIG={baseUrl:"ws://dev-gateway.chinamarket.cn",path:"/api/user-web/websocket/messageServer",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0},t.client=null,t.currentUserId=null,t.currentToken=null,t.config={...t.DEFAULT_CONFIG},exports.MessageSocket=t,exports.WebSocketClient=e;
1
+ "use strict";class e{constructor(t={}){this.socket=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.callbackList=[],this.currentUrl=null,this.reconnectAttempts=0,this.manualClose=!1,this.config={...e.DEFAULT_CONFIG,...t}}updateConfig(e){this.config={...this.config,...e}}connect(e){const t=e||this.config.url;if(t){this.currentUrl=t,this.manualClose=!1;try{this.socket=new WebSocket(t),this.socket.onopen=()=>{console.log("[WebSocketClient] 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),this.onOpen()},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{console.log("[WebSocketClient] 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.onClose(e),!this.manualClose&&this.config.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{console.error("[WebSocketClient] 连接错误",e),this.stopHeartbeat(),this.onError(e)}}catch(e){console.error("[WebSocketClient] 连接失败",e),this.config.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else console.error("[WebSocketClient] 缺少 WebSocket URL")}scheduleReconnect(){if(this.reconnectAttempts>=this.config.maxReconnectAttempts||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=this.config.maxReconnectAttempts&&console.warn("[WebSocketClient] 已达到最大重连次数"));this.reconnectAttempts+=1;const e=Math.min(this.config.reconnectDelay*this.reconnectAttempts,this.config.reconnectDelayMax);console.log(`[WebSocketClient] 将在 ${e}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=window.setTimeout(()=>{this.connect(this.currentUrl)},e)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.currentUrl=null,this.reconnectAttempts=0}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void console.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 console.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){console.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):console.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(){}onClose(e){}onError(e){}onMessage(e){}}e.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,heartbeatMessage:()=>({type:"PING",timestamp:Date.now()})};class t{static configure(e){t.config={...t.config,...e}}static start(n){const{userId:c,token:s,callbacks:o=[]}=n;if(!c||!s)return void console.warn("[MessageSocket] 缺少 userId 或 token,无法启动");if(t.client&&t.client.isConnected()&&t.currentUserId===c&&t.currentToken===s)return console.log("[MessageSocket] 复用现有连接"),void o.forEach(e=>t.registerCallbacks(e));t.stop(),t.currentUserId=c,t.currentToken=s;const{baseUrl:r,path:i,...a}=t.config,l=`${r}${i}/${c}?token=${encodeURIComponent(s)}`;t.client=new e({...a,url:l}),o.forEach(e=>t.registerCallbacks(e)),t.client.connect()}static stop(){t.client&&(t.client.disconnect(),t.client.clearCallbacks(),t.client=null),t.currentUserId=null,t.currentToken=null}static registerCallbacks(e){e?"object"==typeof e?"function"==typeof e.callback?"string"==typeof e.type?t.client?t.client.on(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法注册回调"):console.warn("[MessageSocket] 注册回调失败,type 不是字符串",e):console.warn("[MessageSocket] 注册回调失败,callback 不是函数",e):console.warn("[MessageSocket] 注册回调失败,entry 不是对象",e):console.warn("[MessageSocket] 注册回调失败,缺少 entry",e)}static unregisterCallbacks(e,n){t.client&&t.client.off(e,n)}static send(e){t.client?t.client.send(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法发送消息")}static getReadyState(){return t.client?.getReadyState()??WebSocket.CLOSED}static isConnected(){return t.client?.isConnected()??!1}static getCurrentUserId(){return t.currentUserId}static getCurrentToken(){return t.currentToken}}t.DEFAULT_CONFIG={baseUrl:"wss://dev-gateway.chinamarket.cn",path:"/api/user-web/websocket/messageServer",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0},t.client=null,t.currentUserId=null,t.currentToken=null,t.config={...t.DEFAULT_CONFIG},exports.MessageSocket=t,exports.WebSocketClient=e;
2
2
  //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/WebSocketClient.ts","../src/MessageSocket.ts"],"sourcesContent":["/**\n * WebSocket 配置选项\n */\nexport interface WebSocketConfig {\n /** WebSocket 服务器地址(可选,可以在 connect 时指定) */\n url?: string\n /** 心跳间隔(毫秒),默认 25000 */\n heartbeatInterval?: number\n /** 最大重连次数,默认 10 */\n maxReconnectAttempts?: number\n /** 重连延迟(毫秒),默认 3000 */\n reconnectDelay?: number\n /** 最大重连延迟(毫秒),默认 10000 */\n reconnectDelayMax?: number\n /** 心跳消息生成器 */\n heartbeatMessage?: () => string | object\n /** 是否自动重连,默认 true */\n autoReconnect?: boolean\n}\n\n/**\n * 消息数据结构\n */\nexport interface MessageData<T = unknown> {\n /** 消息类型 */\n type: string\n /** 消息数据 */\n data: T\n /** 元数据 */\n meta?: Record<string, unknown>\n /** 时间戳 */\n timestamp?: number\n}\n\n/**\n * 消息回调函数\n */\nexport type MessageCallback<T = unknown> = (data: T, message?: MessageData<T>) => void\n\n/**\n * 消息回调配置\n */\nexport interface MessageCallbackEntry<T = unknown> {\n /** 消息类型 */\n type: string\n /** 回调函数 */\n callback: MessageCallback<T>\n}\n\n/**\n * WebSocket 基础封装类\n * 提供连接管理、心跳、自动重连、消息回调等基础功能\n */\nexport class WebSocketClient {\n /** WebSocket 连接实例 */\n protected socket: WebSocket | null = null\n\n /** 心跳定时器 */\n protected heartbeatTimer: number | null = null\n\n /** 重连定时器 */\n protected reconnectTimer: number | null = null\n\n /** 消息回调列表 */\n protected callbackList: MessageCallbackEntry<unknown>[] = []\n\n /** 当前连接的 URL */\n protected currentUrl: string | null = null\n\n /** 配置选项 */\n protected config: Required<WebSocketConfig>\n\n /** 重连次数 */\n protected reconnectAttempts = 0\n\n /** 是否手动关闭 */\n protected manualClose = false\n\n /**\n * 默认配置\n */\n protected static readonly DEFAULT_CONFIG: Required<WebSocketConfig> = {\n url: '',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n heartbeatMessage: () => ({\n type: 'PING',\n timestamp: Date.now(),\n }),\n }\n\n /**\n * 构造函数\n * @param config 配置选项\n */\n constructor(config: WebSocketConfig = {}) {\n this.config = { ...WebSocketClient.DEFAULT_CONFIG, ...config }\n }\n\n /**\n * 更新配置\n * @param config 新的配置选项\n */\n public updateConfig(config: Partial<WebSocketConfig>): void {\n this.config = { ...this.config, ...config }\n }\n\n /**\n * 连接到 WebSocket 服务器\n * @param url WebSocket 地址(可选,如果不传则使用配置中的 url)\n */\n public connect(url?: string): void {\n const targetUrl = url || this.config.url\n if (!targetUrl) {\n console.error('[WebSocketClient] 缺少 WebSocket URL')\n return\n }\n\n this.currentUrl = targetUrl\n this.manualClose = false\n\n try {\n this.socket = new WebSocket(targetUrl)\n\n this.socket.onopen = () => {\n console.log('[WebSocketClient] 连接成功')\n this.reconnectAttempts = 0\n this.startHeartbeat()\n this.onOpen()\n }\n\n this.socket.onmessage = (event: MessageEvent) => {\n this.handleIncoming(event.data)\n }\n\n this.socket.onclose = (event: CloseEvent) => {\n console.log('[WebSocketClient] 连接关闭', event.code, event.reason)\n this.stopHeartbeat()\n this.onClose(event)\n\n if (!this.manualClose && this.config.autoReconnect) {\n this.scheduleReconnect()\n }\n }\n\n this.socket.onerror = (event: Event) => {\n console.error('[WebSocketClient] 连接错误', event)\n this.stopHeartbeat()\n this.onError(event)\n }\n } catch (error) {\n console.error('[WebSocketClient] 连接失败', error)\n if (this.config.autoReconnect && !this.manualClose) {\n this.scheduleReconnect()\n }\n }\n }\n\n /**\n * 计划重连\n */\n protected scheduleReconnect(): void {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {\n console.warn('[WebSocketClient] 已达到最大重连次数')\n }\n return\n }\n\n this.reconnectAttempts += 1\n const delay = Math.min(this.config.reconnectDelay * this.reconnectAttempts, this.config.reconnectDelayMax)\n\n console.log(`[WebSocketClient] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`)\n\n this.reconnectTimer = window.setTimeout(() => {\n this.connect(this.currentUrl!)\n }, delay)\n }\n\n /**\n * 断开连接\n */\n public disconnect(): void {\n this.manualClose = true\n this.stopHeartbeat()\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n\n if (this.socket) {\n this.socket.close()\n this.socket = null\n }\n\n this.currentUrl = null\n this.reconnectAttempts = 0\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public send(data: string | object): void {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n console.warn('[WebSocketClient] WebSocket 未连接,无法发送消息')\n return\n }\n\n const message = typeof data === 'string' ? data : JSON.stringify(data)\n this.socket.send(message)\n }\n\n /**\n * 处理接收的消息\n * @param data 接收到的数据\n */\n protected handleIncoming(data: string): void {\n if (!data) return\n\n let message: MessageData\n try {\n message = JSON.parse(data)\n } catch {\n console.warn('[WebSocketClient] 无法解析消息', data)\n return\n }\n\n if (!message?.type) {\n return\n }\n\n // 触发匹配的回调\n const matched = this.callbackList.filter((entry) => entry.type === message.type)\n matched.forEach(({ callback }) => {\n try {\n callback(message.data, message)\n } catch (error) {\n console.error('[WebSocketClient] 回调执行失败', error)\n }\n })\n\n // 调用钩子\n this.onMessage(message)\n }\n\n /**\n * 启动心跳\n */\n protected startHeartbeat(): void {\n this.stopHeartbeat()\n\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n this.heartbeatTimer = window.setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n const heartbeatData = this.config.heartbeatMessage()\n this.send(heartbeatData)\n }, this.config.heartbeatInterval)\n }\n\n /**\n * 停止心跳\n */\n protected stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public on<T = unknown>(type: string, callback: MessageCallback<T>): void\n public on<T = unknown>(entry: MessageCallbackEntry<T>): void\n public on<T = unknown>(typeOrEntry: string | MessageCallbackEntry<T>, callback?: MessageCallback<T>): void {\n const entry: MessageCallbackEntry<T> =\n typeof typeOrEntry === 'string' ? { type: typeOrEntry, callback: callback! } : typeOrEntry\n\n if (!entry.type || typeof entry.callback !== 'function') {\n console.warn('[WebSocketClient] 无效的回调配置', entry)\n return\n }\n\n this.callbackList.push(entry as MessageCallbackEntry<unknown>)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选,如果不传则移除该类型的所有回调)\n */\n public off<T = unknown>(type: string, callback?: MessageCallback<T>): void {\n if (callback) {\n this.callbackList = this.callbackList.filter((entry) => !(entry.type === type && entry.callback === callback))\n } else {\n this.callbackList = this.callbackList.filter((entry) => entry.type !== type)\n }\n }\n\n /**\n * 清空所有回调\n */\n public clearCallbacks(): void {\n this.callbackList = []\n }\n\n /**\n * 获取当前连接状态\n */\n public getReadyState(): number {\n return this.socket?.readyState ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public isConnected(): boolean {\n return this.socket?.readyState === WebSocket.OPEN\n }\n\n // ========== 钩子方法(子类可以重写) ==========\n\n /**\n * 连接打开时的钩子\n */\n protected onOpen(): void {\n // 子类可以重写\n }\n\n /**\n * 连接关闭时的钩子\n */\n protected onClose(_event: CloseEvent): void {\n // 子类可以重写\n }\n\n /**\n * 连接错误时的钩子\n */\n protected onError(_event: Event): void {\n // 子类可以重写\n }\n\n /**\n * 收到消息时的钩子\n */\n protected onMessage(_message: MessageData): void {\n // 子类可以重写\n }\n}\n","import { WebSocketClient, WebSocketConfig, MessageCallbackEntry } from './WebSocketClient'\n\n/**\n * MessageSocket 配置选项\n */\nexport interface MessageSocketConfig extends WebSocketConfig {\n /** WebSocket 服务器基础地址,默认 'ws://dev-gateway.chinamarket.cn' */\n baseUrl?: string\n /** WebSocket 路径,默认 '/api/user-web/websocket/messageServer' */\n path?: string\n}\n\n/**\n * MessageSocket 启动选项\n */\nexport interface MessageSocketStartOptions {\n /** 用户ID(必需) */\n userId: string\n /** 认证令牌(必需) */\n token: string\n /** 消息回调列表 */\n callbacks?: MessageCallbackEntry[]\n}\n\n/**\n * 消息 Socket 类\n * 基于 WebSocketClient,提供用户认证和消息管理功能\n * 用于获取用户未读消息数量等业务场景\n *\n * @example\n * ```typescript\n * // 启动连接\n * MessageSocket.start({\n * userId: '1234567890',\n * token: 'your-token',\n * callbacks: [\n * {\n * type: 'UNREAD_COUNT',\n * callback: (payload) => {\n * console.log('未读消息数:', payload)\n * }\n * }\n * ]\n * })\n *\n * // 停止连接\n * MessageSocket.stop()\n *\n * // 动态注册回调\n * MessageSocket.registerCallbacks({\n * type: 'NEW_MESSAGE',\n * callback: (payload) => {\n * console.log('新消息:', payload)\n * }\n * })\n * ```\n */\nexport class MessageSocket {\n /** 默认配置 */\n private static readonly DEFAULT_CONFIG: MessageSocketConfig = {\n baseUrl: 'ws://dev-gateway.chinamarket.cn',\n path: '/api/user-web/websocket/messageServer',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n }\n\n /** WebSocket 客户端实例 */\n private static client: WebSocketClient | null = null\n\n /** 当前用户ID */\n private static currentUserId: string | null = null\n\n /** 当前token */\n private static currentToken: string | null = null\n\n /** 当前配置 */\n private static config: MessageSocketConfig = { ...MessageSocket.DEFAULT_CONFIG }\n\n /**\n * 配置 MessageSocket\n * @param config 配置选项\n */\n public static configure(config: Partial<MessageSocketConfig>): void {\n MessageSocket.config = { ...MessageSocket.config, ...config }\n }\n\n /**\n * 启动连接\n * @param options 启动选项\n */\n public static start(options: MessageSocketStartOptions): void {\n const { userId, token, callbacks = [] } = options\n\n if (!userId || !token) {\n console.warn('[MessageSocket] 缺少 userId 或 token,无法启动')\n return\n }\n\n // 检查是否可以复用现有连接\n const shouldReuse =\n MessageSocket.client &&\n MessageSocket.client.isConnected() &&\n MessageSocket.currentUserId === userId &&\n MessageSocket.currentToken === token\n\n if (shouldReuse) {\n console.log('[MessageSocket] 复用现有连接')\n // 重新注册回调\n callbacks.forEach((entry) => MessageSocket.registerCallbacks(entry))\n return\n }\n\n // 停止旧连接\n MessageSocket.stop()\n\n // 保存当前用户信息\n MessageSocket.currentUserId = userId\n MessageSocket.currentToken = token\n\n // 构建 WebSocket URL\n const { baseUrl, path, ...clientConfig } = MessageSocket.config\n const url = `${baseUrl}${path}/${userId}?token=${encodeURIComponent(token)}`\n\n // 创建新的 WebSocket 客户端\n MessageSocket.client = new WebSocketClient({\n ...clientConfig,\n url,\n })\n\n // 注册回调\n callbacks.forEach((entry) => MessageSocket.registerCallbacks(entry))\n\n // 连接\n MessageSocket.client.connect()\n }\n\n /**\n * 停止连接\n */\n public static stop(): void {\n if (MessageSocket.client) {\n MessageSocket.client.disconnect()\n MessageSocket.client.clearCallbacks()\n MessageSocket.client = null\n }\n\n MessageSocket.currentUserId = null\n MessageSocket.currentToken = null\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public static registerCallbacks<T = unknown>(entry: MessageCallbackEntry<T>): void {\n if (!entry) {\n console.warn('[MessageSocket] 注册回调失败,缺少 entry', entry)\n return\n }\n\n if (typeof entry !== 'object') {\n console.warn('[MessageSocket] 注册回调失败,entry 不是对象', entry)\n return\n }\n\n if (typeof entry.callback !== 'function') {\n console.warn('[MessageSocket] 注册回调失败,callback 不是函数', entry)\n return\n }\n\n if (typeof entry.type !== 'string') {\n console.warn('[MessageSocket] 注册回调失败,type 不是字符串', entry)\n return\n }\n\n if (!MessageSocket.client) {\n console.warn('[MessageSocket] WebSocket 客户端未初始化,无法注册回调')\n return\n }\n\n MessageSocket.client.on(entry)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选)\n */\n public static unregisterCallbacks<T = unknown>(type: string, callback?: (data: T, message?: unknown) => void): void {\n if (!MessageSocket.client) {\n return\n }\n\n MessageSocket.client.off(type, callback)\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public static send(data: string | object): void {\n if (!MessageSocket.client) {\n console.warn('[MessageSocket] WebSocket 客户端未初始化,无法发送消息')\n return\n }\n\n MessageSocket.client.send(data)\n }\n\n /**\n * 获取连接状态\n */\n public static getReadyState(): number {\n return MessageSocket.client?.getReadyState() ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public static isConnected(): boolean {\n return MessageSocket.client?.isConnected() ?? false\n }\n\n /**\n * 获取当前用户ID\n */\n public static getCurrentUserId(): string | null {\n return MessageSocket.currentUserId\n }\n\n /**\n * 获取当前token\n */\n public static getCurrentToken(): string | null {\n return MessageSocket.currentToken\n }\n}\n"],"names":["WebSocketClient","constructor","config","this","socket","heartbeatTimer","reconnectTimer","callbackList","currentUrl","reconnectAttempts","manualClose","DEFAULT_CONFIG","updateConfig","connect","url","targetUrl","WebSocket","onopen","console","log","startHeartbeat","onOpen","onmessage","event","handleIncoming","data","onclose","code","reason","stopHeartbeat","onClose","autoReconnect","scheduleReconnect","onerror","error","onError","maxReconnectAttempts","warn","delay","Math","min","reconnectDelay","reconnectDelayMax","window","setTimeout","disconnect","clearTimeout","close","send","readyState","OPEN","message","JSON","stringify","parse","type","filter","entry","forEach","callback","onMessage","setInterval","heartbeatData","heartbeatMessage","heartbeatInterval","clearInterval","on","typeOrEntry","push","off","clearCallbacks","getReadyState","CLOSED","isConnected","_event","_message","timestamp","Date","now","MessageSocket","configure","start","options","userId","token","callbacks","client","currentUserId","currentToken","registerCallbacks","stop","baseUrl","path","clientConfig","encodeURIComponent","unregisterCallbacks","getCurrentUserId","getCurrentToken"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/WebSocketClient.ts","../src/MessageSocket.ts"],"sourcesContent":["/**\n * WebSocket 配置选项\n */\nexport interface WebSocketConfig {\n /** WebSocket 服务器地址(可选,可以在 connect 时指定) */\n url?: string\n /** 心跳间隔(毫秒),默认 25000 */\n heartbeatInterval?: number\n /** 最大重连次数,默认 10 */\n maxReconnectAttempts?: number\n /** 重连延迟(毫秒),默认 3000 */\n reconnectDelay?: number\n /** 最大重连延迟(毫秒),默认 10000 */\n reconnectDelayMax?: number\n /** 心跳消息生成器 */\n heartbeatMessage?: () => string | object\n /** 是否自动重连,默认 true */\n autoReconnect?: boolean\n}\n\n/**\n * 消息数据结构\n */\nexport interface MessageData<T = unknown> {\n /** 消息类型 */\n type: string\n /** 消息数据 */\n data: T\n /** 元数据 */\n meta?: Record<string, unknown>\n /** 时间戳 */\n timestamp?: number\n}\n\n/**\n * 消息回调函数\n */\nexport type MessageCallback<T = unknown> = (data: T, message?: MessageData<T>) => void\n\n/**\n * 消息回调配置\n */\nexport interface MessageCallbackEntry<T = unknown> {\n /** 消息类型 */\n type: string\n /** 回调函数 */\n callback: MessageCallback<T>\n}\n\n/**\n * WebSocket 基础封装类\n * 提供连接管理、心跳、自动重连、消息回调等基础功能\n */\nexport class WebSocketClient {\n /** WebSocket 连接实例 */\n protected socket: WebSocket | null = null\n\n /** 心跳定时器 */\n protected heartbeatTimer: number | null = null\n\n /** 重连定时器 */\n protected reconnectTimer: number | null = null\n\n /** 消息回调列表 */\n protected callbackList: MessageCallbackEntry<unknown>[] = []\n\n /** 当前连接的 URL */\n protected currentUrl: string | null = null\n\n /** 配置选项 */\n protected config: Required<WebSocketConfig>\n\n /** 重连次数 */\n protected reconnectAttempts = 0\n\n /** 是否手动关闭 */\n protected manualClose = false\n\n /**\n * 默认配置\n */\n protected static readonly DEFAULT_CONFIG: Required<WebSocketConfig> = {\n url: '',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n heartbeatMessage: () => ({\n type: 'PING',\n timestamp: Date.now(),\n }),\n }\n\n /**\n * 构造函数\n * @param config 配置选项\n */\n constructor(config: WebSocketConfig = {}) {\n this.config = { ...WebSocketClient.DEFAULT_CONFIG, ...config }\n }\n\n /**\n * 更新配置\n * @param config 新的配置选项\n */\n public updateConfig(config: Partial<WebSocketConfig>): void {\n this.config = { ...this.config, ...config }\n }\n\n /**\n * 连接到 WebSocket 服务器\n * @param url WebSocket 地址(可选,如果不传则使用配置中的 url)\n */\n public connect(url?: string): void {\n const targetUrl = url || this.config.url\n if (!targetUrl) {\n console.error('[WebSocketClient] 缺少 WebSocket URL')\n return\n }\n\n this.currentUrl = targetUrl\n this.manualClose = false\n\n try {\n this.socket = new WebSocket(targetUrl)\n\n this.socket.onopen = () => {\n console.log('[WebSocketClient] 连接成功')\n this.reconnectAttempts = 0\n this.startHeartbeat()\n this.onOpen()\n }\n\n this.socket.onmessage = (event: MessageEvent) => {\n this.handleIncoming(event.data)\n }\n\n this.socket.onclose = (event: CloseEvent) => {\n console.log('[WebSocketClient] 连接关闭', event.code, event.reason)\n this.stopHeartbeat()\n this.onClose(event)\n\n if (!this.manualClose && this.config.autoReconnect) {\n this.scheduleReconnect()\n }\n }\n\n this.socket.onerror = (event: Event) => {\n console.error('[WebSocketClient] 连接错误', event)\n this.stopHeartbeat()\n this.onError(event)\n }\n } catch (error) {\n console.error('[WebSocketClient] 连接失败', error)\n if (this.config.autoReconnect && !this.manualClose) {\n this.scheduleReconnect()\n }\n }\n }\n\n /**\n * 计划重连\n */\n protected scheduleReconnect(): void {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts || !this.currentUrl || this.manualClose) {\n if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {\n console.warn('[WebSocketClient] 已达到最大重连次数')\n }\n return\n }\n\n this.reconnectAttempts += 1\n const delay = Math.min(this.config.reconnectDelay * this.reconnectAttempts, this.config.reconnectDelayMax)\n\n console.log(`[WebSocketClient] 将在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连`)\n\n this.reconnectTimer = window.setTimeout(() => {\n this.connect(this.currentUrl!)\n }, delay)\n }\n\n /**\n * 断开连接\n */\n public disconnect(): void {\n this.manualClose = true\n this.stopHeartbeat()\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n\n if (this.socket) {\n this.socket.close()\n this.socket = null\n }\n\n this.currentUrl = null\n this.reconnectAttempts = 0\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public send(data: string | object): void {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n console.warn('[WebSocketClient] WebSocket 未连接,无法发送消息')\n return\n }\n\n const message = typeof data === 'string' ? data : JSON.stringify(data)\n this.socket.send(message)\n }\n\n /**\n * 处理接收的消息\n * @param data 接收到的数据\n */\n protected handleIncoming(data: string): void {\n if (!data) return\n\n let message: MessageData\n try {\n message = JSON.parse(data)\n } catch {\n console.warn('[WebSocketClient] 无法解析消息', data)\n return\n }\n\n if (!message?.type) {\n return\n }\n\n // 触发匹配的回调\n const matched = this.callbackList.filter((entry) => entry.type === message.type)\n matched.forEach(({ callback }) => {\n try {\n callback(message.data, message)\n } catch (error) {\n console.error('[WebSocketClient] 回调执行失败', error)\n }\n })\n\n // 调用钩子\n this.onMessage(message)\n }\n\n /**\n * 启动心跳\n */\n protected startHeartbeat(): void {\n this.stopHeartbeat()\n\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n this.heartbeatTimer = window.setInterval(() => {\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\n return\n }\n\n const heartbeatData = this.config.heartbeatMessage()\n this.send(heartbeatData)\n }, this.config.heartbeatInterval)\n }\n\n /**\n * 停止心跳\n */\n protected stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public on<T = unknown>(type: string, callback: MessageCallback<T>): void\n public on<T = unknown>(entry: MessageCallbackEntry<T>): void\n public on<T = unknown>(typeOrEntry: string | MessageCallbackEntry<T>, callback?: MessageCallback<T>): void {\n const entry: MessageCallbackEntry<T> =\n typeof typeOrEntry === 'string' ? { type: typeOrEntry, callback: callback! } : typeOrEntry\n\n if (!entry.type || typeof entry.callback !== 'function') {\n console.warn('[WebSocketClient] 无效的回调配置', entry)\n return\n }\n\n this.callbackList.push(entry as MessageCallbackEntry<unknown>)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选,如果不传则移除该类型的所有回调)\n */\n public off<T = unknown>(type: string, callback?: MessageCallback<T>): void {\n if (callback) {\n this.callbackList = this.callbackList.filter((entry) => !(entry.type === type && entry.callback === callback))\n } else {\n this.callbackList = this.callbackList.filter((entry) => entry.type !== type)\n }\n }\n\n /**\n * 清空所有回调\n */\n public clearCallbacks(): void {\n this.callbackList = []\n }\n\n /**\n * 获取当前连接状态\n */\n public getReadyState(): number {\n return this.socket?.readyState ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public isConnected(): boolean {\n return this.socket?.readyState === WebSocket.OPEN\n }\n\n // ========== 钩子方法(子类可以重写) ==========\n\n /**\n * 连接打开时的钩子\n */\n protected onOpen(): void {\n // 子类可以重写\n }\n\n /**\n * 连接关闭时的钩子\n */\n protected onClose(_event: CloseEvent): void {\n // 子类可以重写\n }\n\n /**\n * 连接错误时的钩子\n */\n protected onError(_event: Event): void {\n // 子类可以重写\n }\n\n /**\n * 收到消息时的钩子\n */\n protected onMessage(_message: MessageData): void {\n // 子类可以重写\n }\n}\n","import { WebSocketClient, WebSocketConfig, MessageCallbackEntry } from './WebSocketClient'\n\n/**\n * MessageSocket 配置选项\n */\nexport interface MessageSocketConfig extends WebSocketConfig {\n /** WebSocket 服务器基础地址,默认 'ws://dev-gateway.chinamarket.cn' */\n baseUrl?: string\n /** WebSocket 路径,默认 '/api/user-web/websocket/messageServer' */\n path?: string\n}\n\n/**\n * MessageSocket 启动选项\n */\nexport interface MessageSocketStartOptions {\n /** 用户ID(必需) */\n userId: string\n /** 认证令牌(必需) */\n token: string\n /** 消息回调列表 */\n callbacks?: MessageCallbackEntry[]\n}\n\n/**\n * 消息 Socket 类\n * 基于 WebSocketClient,提供用户认证和消息管理功能\n * 用于获取用户未读消息数量等业务场景\n *\n * @example\n * ```typescript\n * // 启动连接\n * MessageSocket.start({\n * userId: '1234567890',\n * token: 'your-token',\n * callbacks: [\n * {\n * type: 'UNREAD_COUNT',\n * callback: (payload) => {\n * console.log('未读消息数:', payload)\n * }\n * }\n * ]\n * })\n *\n * // 停止连接\n * MessageSocket.stop()\n *\n * // 动态注册回调\n * MessageSocket.registerCallbacks({\n * type: 'NEW_MESSAGE',\n * callback: (payload) => {\n * console.log('新消息:', payload)\n * }\n * })\n * ```\n */\nexport class MessageSocket {\n /** 默认配置 */\n private static readonly DEFAULT_CONFIG: MessageSocketConfig = {\n baseUrl: 'wss://dev-gateway.chinamarket.cn',\n path: '/api/user-web/websocket/messageServer',\n heartbeatInterval: 25000,\n maxReconnectAttempts: 10,\n reconnectDelay: 3000,\n reconnectDelayMax: 10000,\n autoReconnect: true,\n }\n\n /** WebSocket 客户端实例 */\n private static client: WebSocketClient | null = null\n\n /** 当前用户ID */\n private static currentUserId: string | null = null\n\n /** 当前token */\n private static currentToken: string | null = null\n\n /** 当前配置 */\n private static config: MessageSocketConfig = { ...MessageSocket.DEFAULT_CONFIG }\n\n /**\n * 配置 MessageSocket\n * @param config 配置选项\n */\n public static configure(config: Partial<MessageSocketConfig>): void {\n MessageSocket.config = { ...MessageSocket.config, ...config }\n }\n\n /**\n * 启动连接\n * @param options 启动选项\n */\n public static start(options: MessageSocketStartOptions): void {\n const { userId, token, callbacks = [] } = options\n\n if (!userId || !token) {\n console.warn('[MessageSocket] 缺少 userId 或 token,无法启动')\n return\n }\n\n // 检查是否可以复用现有连接\n const shouldReuse =\n MessageSocket.client &&\n MessageSocket.client.isConnected() &&\n MessageSocket.currentUserId === userId &&\n MessageSocket.currentToken === token\n\n if (shouldReuse) {\n console.log('[MessageSocket] 复用现有连接')\n // 重新注册回调\n callbacks.forEach((entry) => MessageSocket.registerCallbacks(entry))\n return\n }\n\n // 停止旧连接\n MessageSocket.stop()\n\n // 保存当前用户信息\n MessageSocket.currentUserId = userId\n MessageSocket.currentToken = token\n\n // 构建 WebSocket URL\n const { baseUrl, path, ...clientConfig } = MessageSocket.config\n const url = `${baseUrl}${path}/${userId}?token=${encodeURIComponent(token)}`\n\n // 创建新的 WebSocket 客户端\n MessageSocket.client = new WebSocketClient({\n ...clientConfig,\n url,\n })\n\n // 注册回调\n callbacks.forEach((entry) => MessageSocket.registerCallbacks(entry))\n\n // 连接\n MessageSocket.client.connect()\n }\n\n /**\n * 停止连接\n */\n public static stop(): void {\n if (MessageSocket.client) {\n MessageSocket.client.disconnect()\n MessageSocket.client.clearCallbacks()\n MessageSocket.client = null\n }\n\n MessageSocket.currentUserId = null\n MessageSocket.currentToken = null\n }\n\n /**\n * 注册消息回调\n * @param entry 回调配置\n */\n public static registerCallbacks<T = unknown>(entry: MessageCallbackEntry<T>): void {\n if (!entry) {\n console.warn('[MessageSocket] 注册回调失败,缺少 entry', entry)\n return\n }\n\n if (typeof entry !== 'object') {\n console.warn('[MessageSocket] 注册回调失败,entry 不是对象', entry)\n return\n }\n\n if (typeof entry.callback !== 'function') {\n console.warn('[MessageSocket] 注册回调失败,callback 不是函数', entry)\n return\n }\n\n if (typeof entry.type !== 'string') {\n console.warn('[MessageSocket] 注册回调失败,type 不是字符串', entry)\n return\n }\n\n if (!MessageSocket.client) {\n console.warn('[MessageSocket] WebSocket 客户端未初始化,无法注册回调')\n return\n }\n\n MessageSocket.client.on(entry)\n }\n\n /**\n * 取消注册消息回调\n * @param type 消息类型\n * @param callback 回调函数(可选)\n */\n public static unregisterCallbacks<T = unknown>(type: string, callback?: (data: T, message?: unknown) => void): void {\n if (!MessageSocket.client) {\n return\n }\n\n MessageSocket.client.off(type, callback)\n }\n\n /**\n * 发送消息\n * @param data 消息数据\n */\n public static send(data: string | object): void {\n if (!MessageSocket.client) {\n console.warn('[MessageSocket] WebSocket 客户端未初始化,无法发送消息')\n return\n }\n\n MessageSocket.client.send(data)\n }\n\n /**\n * 获取连接状态\n */\n public static getReadyState(): number {\n return MessageSocket.client?.getReadyState() ?? WebSocket.CLOSED\n }\n\n /**\n * 是否已连接\n */\n public static isConnected(): boolean {\n return MessageSocket.client?.isConnected() ?? false\n }\n\n /**\n * 获取当前用户ID\n */\n public static getCurrentUserId(): string | null {\n return MessageSocket.currentUserId\n }\n\n /**\n * 获取当前token\n */\n public static getCurrentToken(): string | null {\n return MessageSocket.currentToken\n }\n}\n"],"names":["WebSocketClient","constructor","config","this","socket","heartbeatTimer","reconnectTimer","callbackList","currentUrl","reconnectAttempts","manualClose","DEFAULT_CONFIG","updateConfig","connect","url","targetUrl","WebSocket","onopen","console","log","startHeartbeat","onOpen","onmessage","event","handleIncoming","data","onclose","code","reason","stopHeartbeat","onClose","autoReconnect","scheduleReconnect","onerror","error","onError","maxReconnectAttempts","warn","delay","Math","min","reconnectDelay","reconnectDelayMax","window","setTimeout","disconnect","clearTimeout","close","send","readyState","OPEN","message","JSON","stringify","parse","type","filter","entry","forEach","callback","onMessage","setInterval","heartbeatData","heartbeatMessage","heartbeatInterval","clearInterval","on","typeOrEntry","push","off","clearCallbacks","getReadyState","CLOSED","isConnected","_event","_message","timestamp","Date","now","MessageSocket","configure","start","options","userId","token","callbacks","client","currentUserId","currentToken","registerCallbacks","stop","baseUrl","path","clientConfig","encodeURIComponent","unregisterCallbacks","getCurrentUserId","getCurrentToken"],"mappings":"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"}
package/dist/index.esm.js CHANGED
@@ -1,2 +1,2 @@
1
- class e{constructor(t={}){this.socket=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.callbackList=[],this.currentUrl=null,this.reconnectAttempts=0,this.manualClose=!1,this.config={...e.DEFAULT_CONFIG,...t}}updateConfig(e){this.config={...this.config,...e}}connect(e){const t=e||this.config.url;if(t){this.currentUrl=t,this.manualClose=!1;try{this.socket=new WebSocket(t),this.socket.onopen=()=>{console.log("[WebSocketClient] 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),this.onOpen()},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{console.log("[WebSocketClient] 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.onClose(e),!this.manualClose&&this.config.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{console.error("[WebSocketClient] 连接错误",e),this.stopHeartbeat(),this.onError(e)}}catch(e){console.error("[WebSocketClient] 连接失败",e),this.config.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else console.error("[WebSocketClient] 缺少 WebSocket URL")}scheduleReconnect(){if(this.reconnectAttempts>=this.config.maxReconnectAttempts||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=this.config.maxReconnectAttempts&&console.warn("[WebSocketClient] 已达到最大重连次数"));this.reconnectAttempts+=1;const e=Math.min(this.config.reconnectDelay*this.reconnectAttempts,this.config.reconnectDelayMax);console.log(`[WebSocketClient] 将在 ${e}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=window.setTimeout(()=>{this.connect(this.currentUrl)},e)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.currentUrl=null,this.reconnectAttempts=0}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void console.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 console.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){console.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):console.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(){}onClose(e){}onError(e){}onMessage(e){}}e.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,heartbeatMessage:()=>({type:"PING",timestamp:Date.now()})};class t{static configure(e){t.config={...t.config,...e}}static start(n){const{userId:c,token:s,callbacks:o=[]}=n;if(!c||!s)return void console.warn("[MessageSocket] 缺少 userId 或 token,无法启动");if(t.client&&t.client.isConnected()&&t.currentUserId===c&&t.currentToken===s)return console.log("[MessageSocket] 复用现有连接"),void o.forEach(e=>t.registerCallbacks(e));t.stop(),t.currentUserId=c,t.currentToken=s;const{baseUrl:r,path:i,...a}=t.config,l=`${r}${i}/${c}?token=${encodeURIComponent(s)}`;t.client=new e({...a,url:l}),o.forEach(e=>t.registerCallbacks(e)),t.client.connect()}static stop(){t.client&&(t.client.disconnect(),t.client.clearCallbacks(),t.client=null),t.currentUserId=null,t.currentToken=null}static registerCallbacks(e){e?"object"==typeof e?"function"==typeof e.callback?"string"==typeof e.type?t.client?t.client.on(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法注册回调"):console.warn("[MessageSocket] 注册回调失败,type 不是字符串",e):console.warn("[MessageSocket] 注册回调失败,callback 不是函数",e):console.warn("[MessageSocket] 注册回调失败,entry 不是对象",e):console.warn("[MessageSocket] 注册回调失败,缺少 entry",e)}static unregisterCallbacks(e,n){t.client&&t.client.off(e,n)}static send(e){t.client?t.client.send(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法发送消息")}static getReadyState(){return t.client?.getReadyState()??WebSocket.CLOSED}static isConnected(){return t.client?.isConnected()??!1}static getCurrentUserId(){return t.currentUserId}static getCurrentToken(){return t.currentToken}}t.DEFAULT_CONFIG={baseUrl:"ws://dev-gateway.chinamarket.cn",path:"/api/user-web/websocket/messageServer",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0},t.client=null,t.currentUserId=null,t.currentToken=null,t.config={...t.DEFAULT_CONFIG};export{t as MessageSocket,e as WebSocketClient};
1
+ class e{constructor(t={}){this.socket=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.callbackList=[],this.currentUrl=null,this.reconnectAttempts=0,this.manualClose=!1,this.config={...e.DEFAULT_CONFIG,...t}}updateConfig(e){this.config={...this.config,...e}}connect(e){const t=e||this.config.url;if(t){this.currentUrl=t,this.manualClose=!1;try{this.socket=new WebSocket(t),this.socket.onopen=()=>{console.log("[WebSocketClient] 连接成功"),this.reconnectAttempts=0,this.startHeartbeat(),this.onOpen()},this.socket.onmessage=e=>{this.handleIncoming(e.data)},this.socket.onclose=e=>{console.log("[WebSocketClient] 连接关闭",e.code,e.reason),this.stopHeartbeat(),this.onClose(e),!this.manualClose&&this.config.autoReconnect&&this.scheduleReconnect()},this.socket.onerror=e=>{console.error("[WebSocketClient] 连接错误",e),this.stopHeartbeat(),this.onError(e)}}catch(e){console.error("[WebSocketClient] 连接失败",e),this.config.autoReconnect&&!this.manualClose&&this.scheduleReconnect()}}else console.error("[WebSocketClient] 缺少 WebSocket URL")}scheduleReconnect(){if(this.reconnectAttempts>=this.config.maxReconnectAttempts||!this.currentUrl||this.manualClose)return void(this.reconnectAttempts>=this.config.maxReconnectAttempts&&console.warn("[WebSocketClient] 已达到最大重连次数"));this.reconnectAttempts+=1;const e=Math.min(this.config.reconnectDelay*this.reconnectAttempts,this.config.reconnectDelayMax);console.log(`[WebSocketClient] 将在 ${e}ms 后进行第 ${this.reconnectAttempts} 次重连`),this.reconnectTimer=window.setTimeout(()=>{this.connect(this.currentUrl)},e)}disconnect(){this.manualClose=!0,this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket&&(this.socket.close(),this.socket=null),this.currentUrl=null,this.reconnectAttempts=0}send(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)return void console.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 console.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){console.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):console.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(){}onClose(e){}onError(e){}onMessage(e){}}e.DEFAULT_CONFIG={url:"",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0,heartbeatMessage:()=>({type:"PING",timestamp:Date.now()})};class t{static configure(e){t.config={...t.config,...e}}static start(n){const{userId:c,token:s,callbacks:o=[]}=n;if(!c||!s)return void console.warn("[MessageSocket] 缺少 userId 或 token,无法启动");if(t.client&&t.client.isConnected()&&t.currentUserId===c&&t.currentToken===s)return console.log("[MessageSocket] 复用现有连接"),void o.forEach(e=>t.registerCallbacks(e));t.stop(),t.currentUserId=c,t.currentToken=s;const{baseUrl:r,path:i,...a}=t.config,l=`${r}${i}/${c}?token=${encodeURIComponent(s)}`;t.client=new e({...a,url:l}),o.forEach(e=>t.registerCallbacks(e)),t.client.connect()}static stop(){t.client&&(t.client.disconnect(),t.client.clearCallbacks(),t.client=null),t.currentUserId=null,t.currentToken=null}static registerCallbacks(e){e?"object"==typeof e?"function"==typeof e.callback?"string"==typeof e.type?t.client?t.client.on(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法注册回调"):console.warn("[MessageSocket] 注册回调失败,type 不是字符串",e):console.warn("[MessageSocket] 注册回调失败,callback 不是函数",e):console.warn("[MessageSocket] 注册回调失败,entry 不是对象",e):console.warn("[MessageSocket] 注册回调失败,缺少 entry",e)}static unregisterCallbacks(e,n){t.client&&t.client.off(e,n)}static send(e){t.client?t.client.send(e):console.warn("[MessageSocket] WebSocket 客户端未初始化,无法发送消息")}static getReadyState(){return t.client?.getReadyState()??WebSocket.CLOSED}static isConnected(){return t.client?.isConnected()??!1}static getCurrentUserId(){return t.currentUserId}static getCurrentToken(){return t.currentToken}}t.DEFAULT_CONFIG={baseUrl:"wss://dev-gateway.chinamarket.cn",path:"/api/user-web/websocket/messageServer",heartbeatInterval:25e3,maxReconnectAttempts:10,reconnectDelay:3e3,reconnectDelayMax:1e4,autoReconnect:!0},t.client=null,t.currentUserId=null,t.currentToken=null,t.config={...t.DEFAULT_CONFIG};export{t as MessageSocket,e as WebSocketClient};
2
2
  //# sourceMappingURL=index.esm.js.map