a2bei4-utils 1.0.2 → 1.0.4

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.
Files changed (62) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2 -2
  3. package/dist/a2bei4.utils.cjs.js +2080 -1846
  4. package/dist/a2bei4.utils.cjs.js.map +1 -1
  5. package/dist/a2bei4.utils.cjs.min.js +1 -1
  6. package/dist/a2bei4.utils.cjs.min.js.map +1 -1
  7. package/dist/a2bei4.utils.esm.js +2073 -1843
  8. package/dist/a2bei4.utils.esm.js.map +1 -1
  9. package/dist/a2bei4.utils.esm.min.js +1 -1
  10. package/dist/a2bei4.utils.esm.min.js.map +1 -1
  11. package/dist/a2bei4.utils.umd.js +2080 -1846
  12. package/dist/a2bei4.utils.umd.js.map +1 -1
  13. package/dist/a2bei4.utils.umd.min.js +1 -1
  14. package/dist/a2bei4.utils.umd.min.js.map +1 -1
  15. package/dist/arr.cjs +27 -27
  16. package/dist/arr.cjs.map +1 -1
  17. package/dist/arr.js +27 -27
  18. package/dist/arr.js.map +1 -1
  19. package/dist/audio.cjs +274 -274
  20. package/dist/audio.cjs.map +1 -1
  21. package/dist/audio.js +274 -274
  22. package/dist/audio.js.map +1 -1
  23. package/dist/browser.cjs +52 -52
  24. package/dist/browser.cjs.map +1 -1
  25. package/dist/browser.js +52 -52
  26. package/dist/browser.js.map +1 -1
  27. package/dist/common.cjs +369 -369
  28. package/dist/common.cjs.map +1 -1
  29. package/dist/common.js +369 -369
  30. package/dist/common.js.map +1 -1
  31. package/dist/date.cjs +421 -188
  32. package/dist/date.cjs.map +1 -1
  33. package/dist/date.js +414 -185
  34. package/dist/date.js.map +1 -1
  35. package/dist/download.cjs +102 -102
  36. package/dist/download.cjs.map +1 -1
  37. package/dist/download.js +102 -102
  38. package/dist/download.js.map +1 -1
  39. package/dist/evt.cjs +148 -148
  40. package/dist/evt.cjs.map +1 -1
  41. package/dist/evt.js +148 -148
  42. package/dist/evt.js.map +1 -1
  43. package/dist/id.cjs +68 -68
  44. package/dist/id.cjs.map +1 -1
  45. package/dist/id.js +68 -68
  46. package/dist/id.js.map +1 -1
  47. package/dist/timer.cjs +51 -50
  48. package/dist/timer.cjs.map +1 -1
  49. package/dist/timer.js +51 -50
  50. package/dist/timer.js.map +1 -1
  51. package/dist/tree.cjs +165 -165
  52. package/dist/tree.cjs.map +1 -1
  53. package/dist/tree.js +165 -165
  54. package/dist/tree.js.map +1 -1
  55. package/dist/webSocket.cjs +403 -403
  56. package/dist/webSocket.cjs.map +1 -1
  57. package/dist/webSocket.js +403 -403
  58. package/dist/webSocket.js.map +1 -1
  59. package/package.json +1 -1
  60. package/readme.txt +21 -11
  61. package/types/date.d.ts +243 -45
  62. package/types/index.d.ts +244 -47
@@ -1,408 +1,408 @@
1
1
  'use strict';
2
2
 
3
- /**
4
- * @class WebSocketManager - 一个纯粹、强大的 WebSocket 连接管理引擎
5
- *
6
- * @features
7
- * - 智能断线重连 (指数退避算法)
8
- * - 网络状态监听
9
- * - 高度可定制的心跳保活机制 (通过注入函数实现)
10
- * - 页面可见性 API 集成
11
- * - 消息发送队列
12
- * - 清晰的生命周期管理
13
- * - 优雅的资源销毁
14
- * - 可配置的数据序列化/反序列化
15
- * - 纯粹的消息传递,不关心消息内容
16
- */
17
- class WebSocketManager {
18
- /**
19
- * @param {string} url - WebSocket 服务器的地址
20
- * @param {object} [options={}] - 配置选项
21
- * @param {number} [options.heartbeatInterval=30000] - 心跳间隔 (毫秒)
22
- * @param {number} [options.heartbeatTimeout=10000] - 心跳超时时间 (毫秒)
23
- * @param {number} [options.reconnectBaseInterval=1000] - 重连基础间隔 (毫秒)
24
- * @param {number} [options.maxReconnectInterval=30000] - 最大重连间隔 (毫秒)
25
- * @param {number} [options.maxReconnectAttempts=Infinity] - 最大重连次数
26
- * @param {boolean} [options.autoConnect=true] - 是否在实例化后自动连接
27
- * @param {boolean} [options.serializeData=false] - 发送数据时是否自动序列化为JSON字符串
28
- * @param {boolean} [options.deserializeData=false] - 接收数据时是否自动反序列化为JSON对象
29
- * @param {function|null} [options.getPingMessage=null] - 返回要发送的 ping 消息内容的函数。如果为 null,则不发送心跳。
30
- * @param {function|null} [options.isPongMessage=null] - 判断接收到的消息是否为 pong 的函数。接收 MessageEvent 对象作为参数,返回布尔值。
31
- * @param {object} [options.protocols] - WebSocket 协议
32
- */
33
- constructor(url, options = {}) {
34
- this.url = url;
35
-
36
- // 默认的心跳实现,用于向后兼容
37
- const defaultGetPingMessage = () => JSON.stringify({ type: "ping" });
38
- const defaultIsPongMessage = (event) => {
39
- try {
40
- const message = JSON.parse(event.data);
41
- return message.type === "pong";
42
- } catch (e) {
43
- return false;
44
- }
45
- };
46
-
47
- this.options = {
48
- heartbeatInterval: 30000,
49
- heartbeatTimeout: 10000,
50
- reconnectBaseInterval: 1000,
51
- maxReconnectInterval: 30000,
52
- maxReconnectAttempts: Infinity,
53
- autoConnect: true,
54
- serializeData: false,
55
- deserializeData: false,
56
- getPingMessage: defaultGetPingMessage, // 默认提供 ping 消息生成器
57
- isPongMessage: defaultIsPongMessage, // 默认提供 pong 消息判断器
58
- ...options
59
- };
60
-
61
- // WebSocket 实例
62
- this.ws = null;
63
-
64
- // 状态管理
65
- this.readyState = WebSocket.CLOSED;
66
- this.reconnectAttempts = 0;
67
- this.forcedClose = false;
68
- this.isReconnecting = false;
69
-
70
- // 定时器
71
- this.heartbeatTimer = null;
72
- this.heartbeatTimeoutTimer = null;
73
- this.reconnectTimer = null;
74
-
75
- // 消息队列
76
- this.messageQueue = [];
77
-
78
- // 事件监听器
79
- this.listeners = new Map();
80
-
81
- // 绑定方法上下文
82
- this._onOpen = this._onOpen.bind(this);
83
- this._onMessage = this._onMessage.bind(this);
84
- this._onClose = this._onClose.bind(this);
85
- this._onError = this._onError.bind(this);
86
- this._handleVisibilityChange = this._handleVisibilityChange.bind(this);
87
- this._handleOnline = this._handleOnline.bind(this);
88
- this._handleOffline = this._handleOffline.bind(this);
89
-
90
- this._setupEventListeners();
91
-
92
- if (this.options.autoConnect) {
93
- this.connect();
94
- }
95
- }
96
-
97
- // --- 公共 API ---
98
-
99
- /**
100
- * 连接到 WebSocket 服务器
101
- */
102
- connect() {
103
- if (this.ws && (this.ws.readyState === WebSocket.CONNECTING || this.ws.readyState === WebSocket.OPEN)) {
104
- return;
105
- }
106
-
107
- this.forcedClose = false;
108
- this._updateReadyState(WebSocket.CONNECTING);
109
- console.log(`[WS] 正在连接到 ${this.url}...`);
110
- this._emit("connecting");
111
-
112
- try {
113
- this.ws = new WebSocket(this.url, this.options.protocols);
114
- this.ws.onopen = this._onOpen;
115
- this.ws.onmessage = this._onMessage;
116
- this.ws.onclose = this._onClose;
117
- this.ws.onerror = this._onError;
118
- } catch (error) {
119
- console.error("[WS] 连接失败:", error);
120
- this._onError(error);
121
- }
122
- }
123
-
124
- /**
125
- * 发送数据
126
- * @param {string|object|ArrayBuffer|Blob} data - 要发送的数据
127
- */
128
- send(data) {
129
- if (this.readyState === WebSocket.OPEN) {
130
- let message;
131
- // 根据配置决定是否序列化数据
132
- if (this.options.serializeData) {
133
- message = JSON.stringify(data);
134
- } else {
135
- message = data;
136
- }
137
- this.ws.send(message);
138
- console.log("[WS] 消息已发送:", message);
139
- } else {
140
- console.warn("[WS] 连接未打开,消息已加入队列:", data);
141
- this.messageQueue.push(data);
142
- }
143
- }
144
-
145
- /**
146
- * 主动关闭连接
147
- * @param {number} [code=1000] - 关闭代码
148
- * @param {string} [reason=''] - 关闭原因
149
- */
150
- close(code = 1000, reason = "Normal closure") {
151
- this.forcedClose = true;
152
- this._stopHeartbeat();
153
- this._clearReconnectTimer();
154
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
155
- this.ws.close(code, reason);
156
- } else {
157
- this._updateReadyState(WebSocket.CLOSED);
158
- this._emit("close", { code, reason, wasClean: true });
159
- }
160
- }
161
-
162
- /**
163
- * 彻底销毁实例,清理所有资源
164
- */
165
- destroy() {
166
- console.log("[WS] 正在销毁实例...");
167
- this.close(1000, "Instance destroyed");
168
- this._removeEventListeners();
169
- this.messageQueue = [];
170
- this.listeners.clear();
171
- this.ws = null;
172
- }
173
-
174
- /**
175
- * 添加事件监听器
176
- * @param {string} eventName - 事件名 (e.g., 'open', 'message', 'close', 'error', 'reconnect')
177
- * @param {function} callback - 回调函数
178
- */
179
- on(eventName, callback) {
180
- if (!this.listeners.has(eventName)) {
181
- this.listeners.set(eventName, []);
182
- }
183
- this.listeners.get(eventName).push(callback);
184
- }
185
-
186
- /**
187
- * 移除事件监听器
188
- * @param {string} eventName - 事件名
189
- * @param {function} callback - 回调函数
190
- */
191
- off(eventName, callback) {
192
- if (this.listeners.has(eventName)) {
193
- const callbacks = this.listeners.get(eventName);
194
- const index = callbacks.indexOf(callback);
195
- if (index > -1) {
196
- callbacks.splice(index, 1);
197
- }
198
- }
199
- }
200
-
201
- // --- 内部方法 ---
202
-
203
- _onOpen(event) {
204
- console.log("[WS] 连接已建立");
205
- this._updateReadyState(WebSocket.OPEN);
206
- this.reconnectAttempts = 0;
207
- this.isReconnecting = false;
208
-
209
- // 如果配置了 getPingMessage,则启动心跳
210
- if (this.options.getPingMessage) {
211
- this._startHeartbeat();
212
- }
213
-
214
- this._flushMessageQueue();
215
- this._emit("open", event);
216
- }
217
-
218
- /**
219
- * 纯粹的消息处理方法:处理心跳,然后将数据交给使用者
220
- * @param {MessageEvent} event
221
- */
222
- _onMessage(event) {
223
- // --- 步骤 1: 使用注入的函数优先处理内部心跳机制 ---
224
- if (this.options.isPongMessage && this.options.isPongMessage(event)) {
225
- this._handlePong();
226
- return; // 是心跳消息,处理完毕,直接返回
227
- }
228
-
229
- // --- 步骤 2: 根据用户配置处理业务消息 ---
230
- if (this.options.deserializeData) {
231
- try {
232
- const parsedMessage = JSON.parse(event.data);
233
- this._emit("message", parsedMessage, event);
234
- } catch (e) {
235
- this._emit("message", event.data, event);
236
- }
237
- } else {
238
- this._emit("message", event.data, event);
239
- }
240
- }
241
-
242
- _onClose(event) {
243
- console.log("[WS] 连接已关闭", event);
244
- this._updateReadyState(WebSocket.CLOSED);
245
- this._stopHeartbeat();
246
- this._emit("close", event);
247
-
248
- if (!this.forcedClose) {
249
- this._scheduleReconnect();
250
- }
251
- }
252
-
253
- _onError(event) {
254
- console.error("[WS] 连接发生错误:", event);
255
- this._emit("error", event);
256
- }
257
-
258
- // --- 心跳机制 ---
259
- _startHeartbeat() {
260
- // 如果没有配置 getPingMessage,则无法启动心跳
261
- if (!this.options.getPingMessage) {
262
- return;
263
- }
264
-
265
- this._stopHeartbeat();
266
- this.heartbeatTimer = setInterval(() => {
267
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
268
- try {
269
- // 调用注入的函数获取消息内容并发送
270
- const pingMessage = this.options.getPingMessage();
271
- this.ws.send(pingMessage);
272
- console.log("[WS] 发送 Ping:", pingMessage);
273
- this._setHeartbeatTimeout();
274
- } catch (error) {
275
- console.error("[WS] 发送 Ping 消息失败:", error);
276
- }
277
- }
278
- }, this.options.heartbeatInterval);
279
- }
280
-
281
- _stopHeartbeat() {
282
- if (this.heartbeatTimer) {
283
- clearInterval(this.heartbeatTimer);
284
- this.heartbeatTimer = null;
285
- }
286
- this._clearHeartbeatTimeout();
287
- }
288
-
289
- _setHeartbeatTimeout() {
290
- this._clearHeartbeatTimeout();
291
- this.heartbeatTimeoutTimer = setTimeout(() => {
292
- console.error("[WS] 心跳超时,主动断开连接");
293
- this.ws.close(1006, "Heartbeat timeout");
294
- }, this.options.heartbeatTimeout);
295
- }
296
-
297
- _clearHeartbeatTimeout() {
298
- if (this.heartbeatTimeoutTimer) {
299
- clearTimeout(this.heartbeatTimeoutTimer);
300
- this.heartbeatTimeoutTimer = null;
301
- }
302
- }
303
-
304
- _handlePong() {
305
- console.log("[WS] 收到 Pong");
306
- this._clearHeartbeatTimeout();
307
- }
308
-
309
- // --- 重连机制 ---
310
- _scheduleReconnect() {
311
- if (this.forcedClose || this.isReconnecting || this.reconnectAttempts >= this.options.maxReconnectAttempts) {
312
- if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
313
- console.error("[WS] 已达到最大重连次数,停止重连");
314
- this._emit("reconnect-failed");
315
- }
316
- return;
317
- }
318
-
319
- this.isReconnecting = true;
320
- const interval = Math.min(this.options.reconnectBaseInterval * Math.pow(2, this.reconnectAttempts), this.options.maxReconnectInterval);
321
-
322
- console.log(`[WS] ${interval / 1000}秒后将尝试第 ${this.reconnectAttempts + 1} 次重连...`);
323
- this._emit("reconnect-attempt", { attempt: this.reconnectAttempts + 1, interval });
324
-
325
- this.reconnectTimer = setTimeout(() => {
326
- this.reconnectAttempts++;
327
- this.connect();
328
- }, interval);
329
- }
330
-
331
- _clearReconnectTimer() {
332
- if (this.reconnectTimer) {
333
- clearTimeout(this.reconnectTimer);
334
- this.reconnectTimer = null;
335
- }
336
- }
337
-
338
- // --- 消息队列 ---
339
- _flushMessageQueue() {
340
- if (this.messageQueue.length === 0) return;
341
- console.log(`[WS] 发送队列中的 ${this.messageQueue.length} 条消息`);
342
- const queue = [...this.messageQueue];
343
- this.messageQueue = [];
344
- queue.forEach((data) => this.send(data));
345
- }
346
-
347
- // --- 事件系统 ---
348
- _emit(eventName, ...args) {
349
- if (this.listeners.has(eventName)) {
350
- this.listeners.get(eventName).forEach((callback) => callback(...args));
351
- }
352
- }
353
-
354
- // --- 状态与监听器管理 ---
355
- _updateReadyState(newState) {
356
- this.readyState = newState;
357
- this._emit("ready-state-change", newState);
358
- }
359
-
360
- _setupEventListeners() {
361
- document.addEventListener("visibilitychange", this._handleVisibilityChange);
362
- window.addEventListener("online", this._handleOnline);
363
- window.addEventListener("offline", this._handleOffline);
364
- }
365
-
366
- _removeEventListeners() {
367
- document.removeEventListener("visibilitychange", this._handleVisibilityChange);
368
- window.removeEventListener("online", this._handleOnline);
369
- window.removeEventListener("offline", this._handleOffline);
370
- }
371
-
372
- _handleVisibilityChange() {
373
- // 如果未启用心跳,不需要处理页面可见性变化
374
- if (!this.options.getPingMessage) {
375
- return;
376
- }
377
-
378
- if (document.hidden) {
379
- console.log("[WS] 页面隐藏,停止心跳");
380
- this._stopHeartbeat();
381
- } else {
382
- console.log("[WS] 页面可见,检查连接状态");
383
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
384
- this._startHeartbeat();
385
- } else if (!this.forcedClose && !this.isReconnecting) {
386
- this.connect();
387
- }
388
- }
389
- }
390
-
391
- _handleOnline() {
392
- console.log("[WS] 网络已恢复,尝试重连");
393
- if (!this.forcedClose && this.readyState !== WebSocket.OPEN) {
394
- this._clearReconnectTimer(); // 清除当前的重连计划
395
- this.connect(); // 立即尝试连接
396
- }
397
- }
398
-
399
- _handleOffline() {
400
- console.log("[WS] 网络已断开");
401
- this._clearReconnectTimer(); // 停止重连尝试
402
- // ws.onclose 会被触发,从而启动重连逻辑,但我们已经停止了
403
- // 所以这里可以手动触发一次 close 事件来通知应用层
404
- if (this.ws) this.ws.onclose({ code: 1006, reason: "Network offline" });
405
- }
3
+ /**
4
+ * @class WebSocketManager - 一个纯粹、强大的 WebSocket 连接管理引擎
5
+ *
6
+ * @features
7
+ * - 智能断线重连 (指数退避算法)
8
+ * - 网络状态监听
9
+ * - 高度可定制的心跳保活机制 (通过注入函数实现)
10
+ * - 页面可见性 API 集成
11
+ * - 消息发送队列
12
+ * - 清晰的生命周期管理
13
+ * - 优雅的资源销毁
14
+ * - 可配置的数据序列化/反序列化
15
+ * - 纯粹的消息传递,不关心消息内容
16
+ */
17
+ class WebSocketManager {
18
+ /**
19
+ * @param {string} url - WebSocket 服务器的地址
20
+ * @param {object} [options={}] - 配置选项
21
+ * @param {number} [options.heartbeatInterval=30000] - 心跳间隔 (毫秒)
22
+ * @param {number} [options.heartbeatTimeout=10000] - 心跳超时时间 (毫秒)
23
+ * @param {number} [options.reconnectBaseInterval=1000] - 重连基础间隔 (毫秒)
24
+ * @param {number} [options.maxReconnectInterval=30000] - 最大重连间隔 (毫秒)
25
+ * @param {number} [options.maxReconnectAttempts=Infinity] - 最大重连次数
26
+ * @param {boolean} [options.autoConnect=true] - 是否在实例化后自动连接
27
+ * @param {boolean} [options.serializeData=false] - 发送数据时是否自动序列化为JSON字符串
28
+ * @param {boolean} [options.deserializeData=false] - 接收数据时是否自动反序列化为JSON对象
29
+ * @param {function|null} [options.getPingMessage=null] - 返回要发送的 ping 消息内容的函数。如果为 null,则不发送心跳。
30
+ * @param {function|null} [options.isPongMessage=null] - 判断接收到的消息是否为 pong 的函数。接收 MessageEvent 对象作为参数,返回布尔值。
31
+ * @param {object} [options.protocols] - WebSocket 协议
32
+ */
33
+ constructor(url, options = {}) {
34
+ this.url = url;
35
+
36
+ // 默认的心跳实现,用于向后兼容
37
+ const defaultGetPingMessage = () => JSON.stringify({ type: "ping" });
38
+ const defaultIsPongMessage = (event) => {
39
+ try {
40
+ const message = JSON.parse(event.data);
41
+ return message.type === "pong";
42
+ } catch (e) {
43
+ return false;
44
+ }
45
+ };
46
+
47
+ this.options = {
48
+ heartbeatInterval: 30000,
49
+ heartbeatTimeout: 10000,
50
+ reconnectBaseInterval: 1000,
51
+ maxReconnectInterval: 30000,
52
+ maxReconnectAttempts: Infinity,
53
+ autoConnect: true,
54
+ serializeData: false,
55
+ deserializeData: false,
56
+ getPingMessage: defaultGetPingMessage, // 默认提供 ping 消息生成器
57
+ isPongMessage: defaultIsPongMessage, // 默认提供 pong 消息判断器
58
+ ...options
59
+ };
60
+
61
+ // WebSocket 实例
62
+ this.ws = null;
63
+
64
+ // 状态管理
65
+ this.readyState = WebSocket.CLOSED;
66
+ this.reconnectAttempts = 0;
67
+ this.forcedClose = false;
68
+ this.isReconnecting = false;
69
+
70
+ // 定时器
71
+ this.heartbeatTimer = null;
72
+ this.heartbeatTimeoutTimer = null;
73
+ this.reconnectTimer = null;
74
+
75
+ // 消息队列
76
+ this.messageQueue = [];
77
+
78
+ // 事件监听器
79
+ this.listeners = new Map();
80
+
81
+ // 绑定方法上下文
82
+ this._onOpen = this._onOpen.bind(this);
83
+ this._onMessage = this._onMessage.bind(this);
84
+ this._onClose = this._onClose.bind(this);
85
+ this._onError = this._onError.bind(this);
86
+ this._handleVisibilityChange = this._handleVisibilityChange.bind(this);
87
+ this._handleOnline = this._handleOnline.bind(this);
88
+ this._handleOffline = this._handleOffline.bind(this);
89
+
90
+ this._setupEventListeners();
91
+
92
+ if (this.options.autoConnect) {
93
+ this.connect();
94
+ }
95
+ }
96
+
97
+ // --- 公共 API ---
98
+
99
+ /**
100
+ * 连接到 WebSocket 服务器
101
+ */
102
+ connect() {
103
+ if (this.ws && (this.ws.readyState === WebSocket.CONNECTING || this.ws.readyState === WebSocket.OPEN)) {
104
+ return;
105
+ }
106
+
107
+ this.forcedClose = false;
108
+ this._updateReadyState(WebSocket.CONNECTING);
109
+ console.log(`[WS] 正在连接到 ${this.url}...`);
110
+ this._emit("connecting");
111
+
112
+ try {
113
+ this.ws = new WebSocket(this.url, this.options.protocols);
114
+ this.ws.onopen = this._onOpen;
115
+ this.ws.onmessage = this._onMessage;
116
+ this.ws.onclose = this._onClose;
117
+ this.ws.onerror = this._onError;
118
+ } catch (error) {
119
+ console.error("[WS] 连接失败:", error);
120
+ this._onError(error);
121
+ }
122
+ }
123
+
124
+ /**
125
+ * 发送数据
126
+ * @param {string|object|ArrayBuffer|Blob} data - 要发送的数据
127
+ */
128
+ send(data) {
129
+ if (this.readyState === WebSocket.OPEN) {
130
+ let message;
131
+ // 根据配置决定是否序列化数据
132
+ if (this.options.serializeData) {
133
+ message = JSON.stringify(data);
134
+ } else {
135
+ message = data;
136
+ }
137
+ this.ws.send(message);
138
+ console.log("[WS] 消息已发送:", message);
139
+ } else {
140
+ console.warn("[WS] 连接未打开,消息已加入队列:", data);
141
+ this.messageQueue.push(data);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * 主动关闭连接
147
+ * @param {number} [code=1000] - 关闭代码
148
+ * @param {string} [reason=''] - 关闭原因
149
+ */
150
+ close(code = 1000, reason = "Normal closure") {
151
+ this.forcedClose = true;
152
+ this._stopHeartbeat();
153
+ this._clearReconnectTimer();
154
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
155
+ this.ws.close(code, reason);
156
+ } else {
157
+ this._updateReadyState(WebSocket.CLOSED);
158
+ this._emit("close", { code, reason, wasClean: true });
159
+ }
160
+ }
161
+
162
+ /**
163
+ * 彻底销毁实例,清理所有资源
164
+ */
165
+ destroy() {
166
+ console.log("[WS] 正在销毁实例...");
167
+ this.close(1000, "Instance destroyed");
168
+ this._removeEventListeners();
169
+ this.messageQueue = [];
170
+ this.listeners.clear();
171
+ this.ws = null;
172
+ }
173
+
174
+ /**
175
+ * 添加事件监听器
176
+ * @param {string} eventName - 事件名 (e.g., 'open', 'message', 'close', 'error', 'reconnect')
177
+ * @param {function} callback - 回调函数
178
+ */
179
+ on(eventName, callback) {
180
+ if (!this.listeners.has(eventName)) {
181
+ this.listeners.set(eventName, []);
182
+ }
183
+ this.listeners.get(eventName).push(callback);
184
+ }
185
+
186
+ /**
187
+ * 移除事件监听器
188
+ * @param {string} eventName - 事件名
189
+ * @param {function} callback - 回调函数
190
+ */
191
+ off(eventName, callback) {
192
+ if (this.listeners.has(eventName)) {
193
+ const callbacks = this.listeners.get(eventName);
194
+ const index = callbacks.indexOf(callback);
195
+ if (index > -1) {
196
+ callbacks.splice(index, 1);
197
+ }
198
+ }
199
+ }
200
+
201
+ // --- 内部方法 ---
202
+
203
+ _onOpen(event) {
204
+ console.log("[WS] 连接已建立");
205
+ this._updateReadyState(WebSocket.OPEN);
206
+ this.reconnectAttempts = 0;
207
+ this.isReconnecting = false;
208
+
209
+ // 如果配置了 getPingMessage,则启动心跳
210
+ if (this.options.getPingMessage) {
211
+ this._startHeartbeat();
212
+ }
213
+
214
+ this._flushMessageQueue();
215
+ this._emit("open", event);
216
+ }
217
+
218
+ /**
219
+ * 纯粹的消息处理方法:处理心跳,然后将数据交给使用者
220
+ * @param {MessageEvent} event
221
+ */
222
+ _onMessage(event) {
223
+ // --- 步骤 1: 使用注入的函数优先处理内部心跳机制 ---
224
+ if (this.options.isPongMessage && this.options.isPongMessage(event)) {
225
+ this._handlePong();
226
+ return; // 是心跳消息,处理完毕,直接返回
227
+ }
228
+
229
+ // --- 步骤 2: 根据用户配置处理业务消息 ---
230
+ if (this.options.deserializeData) {
231
+ try {
232
+ const parsedMessage = JSON.parse(event.data);
233
+ this._emit("message", parsedMessage, event);
234
+ } catch (e) {
235
+ this._emit("message", event.data, event);
236
+ }
237
+ } else {
238
+ this._emit("message", event.data, event);
239
+ }
240
+ }
241
+
242
+ _onClose(event) {
243
+ console.log("[WS] 连接已关闭", event);
244
+ this._updateReadyState(WebSocket.CLOSED);
245
+ this._stopHeartbeat();
246
+ this._emit("close", event);
247
+
248
+ if (!this.forcedClose) {
249
+ this._scheduleReconnect();
250
+ }
251
+ }
252
+
253
+ _onError(event) {
254
+ console.error("[WS] 连接发生错误:", event);
255
+ this._emit("error", event);
256
+ }
257
+
258
+ // --- 心跳机制 ---
259
+ _startHeartbeat() {
260
+ // 如果没有配置 getPingMessage,则无法启动心跳
261
+ if (!this.options.getPingMessage) {
262
+ return;
263
+ }
264
+
265
+ this._stopHeartbeat();
266
+ this.heartbeatTimer = setInterval(() => {
267
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
268
+ try {
269
+ // 调用注入的函数获取消息内容并发送
270
+ const pingMessage = this.options.getPingMessage();
271
+ this.ws.send(pingMessage);
272
+ console.log("[WS] 发送 Ping:", pingMessage);
273
+ this._setHeartbeatTimeout();
274
+ } catch (error) {
275
+ console.error("[WS] 发送 Ping 消息失败:", error);
276
+ }
277
+ }
278
+ }, this.options.heartbeatInterval);
279
+ }
280
+
281
+ _stopHeartbeat() {
282
+ if (this.heartbeatTimer) {
283
+ clearInterval(this.heartbeatTimer);
284
+ this.heartbeatTimer = null;
285
+ }
286
+ this._clearHeartbeatTimeout();
287
+ }
288
+
289
+ _setHeartbeatTimeout() {
290
+ this._clearHeartbeatTimeout();
291
+ this.heartbeatTimeoutTimer = setTimeout(() => {
292
+ console.error("[WS] 心跳超时,主动断开连接");
293
+ this.ws.close(1006, "Heartbeat timeout");
294
+ }, this.options.heartbeatTimeout);
295
+ }
296
+
297
+ _clearHeartbeatTimeout() {
298
+ if (this.heartbeatTimeoutTimer) {
299
+ clearTimeout(this.heartbeatTimeoutTimer);
300
+ this.heartbeatTimeoutTimer = null;
301
+ }
302
+ }
303
+
304
+ _handlePong() {
305
+ console.log("[WS] 收到 Pong");
306
+ this._clearHeartbeatTimeout();
307
+ }
308
+
309
+ // --- 重连机制 ---
310
+ _scheduleReconnect() {
311
+ if (this.forcedClose || this.isReconnecting || this.reconnectAttempts >= this.options.maxReconnectAttempts) {
312
+ if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
313
+ console.error("[WS] 已达到最大重连次数,停止重连");
314
+ this._emit("reconnect-failed");
315
+ }
316
+ return;
317
+ }
318
+
319
+ this.isReconnecting = true;
320
+ const interval = Math.min(this.options.reconnectBaseInterval * Math.pow(2, this.reconnectAttempts), this.options.maxReconnectInterval);
321
+
322
+ console.log(`[WS] ${interval / 1000}秒后将尝试第 ${this.reconnectAttempts + 1} 次重连...`);
323
+ this._emit("reconnect-attempt", { attempt: this.reconnectAttempts + 1, interval });
324
+
325
+ this.reconnectTimer = setTimeout(() => {
326
+ this.reconnectAttempts++;
327
+ this.connect();
328
+ }, interval);
329
+ }
330
+
331
+ _clearReconnectTimer() {
332
+ if (this.reconnectTimer) {
333
+ clearTimeout(this.reconnectTimer);
334
+ this.reconnectTimer = null;
335
+ }
336
+ }
337
+
338
+ // --- 消息队列 ---
339
+ _flushMessageQueue() {
340
+ if (this.messageQueue.length === 0) return;
341
+ console.log(`[WS] 发送队列中的 ${this.messageQueue.length} 条消息`);
342
+ const queue = [...this.messageQueue];
343
+ this.messageQueue = [];
344
+ queue.forEach((data) => this.send(data));
345
+ }
346
+
347
+ // --- 事件系统 ---
348
+ _emit(eventName, ...args) {
349
+ if (this.listeners.has(eventName)) {
350
+ this.listeners.get(eventName).forEach((callback) => callback(...args));
351
+ }
352
+ }
353
+
354
+ // --- 状态与监听器管理 ---
355
+ _updateReadyState(newState) {
356
+ this.readyState = newState;
357
+ this._emit("ready-state-change", newState);
358
+ }
359
+
360
+ _setupEventListeners() {
361
+ document.addEventListener("visibilitychange", this._handleVisibilityChange);
362
+ window.addEventListener("online", this._handleOnline);
363
+ window.addEventListener("offline", this._handleOffline);
364
+ }
365
+
366
+ _removeEventListeners() {
367
+ document.removeEventListener("visibilitychange", this._handleVisibilityChange);
368
+ window.removeEventListener("online", this._handleOnline);
369
+ window.removeEventListener("offline", this._handleOffline);
370
+ }
371
+
372
+ _handleVisibilityChange() {
373
+ // 如果未启用心跳,不需要处理页面可见性变化
374
+ if (!this.options.getPingMessage) {
375
+ return;
376
+ }
377
+
378
+ if (document.hidden) {
379
+ console.log("[WS] 页面隐藏,停止心跳");
380
+ this._stopHeartbeat();
381
+ } else {
382
+ console.log("[WS] 页面可见,检查连接状态");
383
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
384
+ this._startHeartbeat();
385
+ } else if (!this.forcedClose && !this.isReconnecting) {
386
+ this.connect();
387
+ }
388
+ }
389
+ }
390
+
391
+ _handleOnline() {
392
+ console.log("[WS] 网络已恢复,尝试重连");
393
+ if (!this.forcedClose && this.readyState !== WebSocket.OPEN) {
394
+ this._clearReconnectTimer(); // 清除当前的重连计划
395
+ this.connect(); // 立即尝试连接
396
+ }
397
+ }
398
+
399
+ _handleOffline() {
400
+ console.log("[WS] 网络已断开");
401
+ this._clearReconnectTimer(); // 停止重连尝试
402
+ // ws.onclose 会被触发,从而启动重连逻辑,但我们已经停止了
403
+ // 所以这里可以手动触发一次 close 事件来通知应用层
404
+ if (this.ws) this.ws.onclose({ code: 1006, reason: "Network offline" });
405
+ }
406
406
  }
407
407
 
408
408
  exports.WebSocketManager = WebSocketManager;