a2bei4-utils 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2 -2
  3. package/dist/a2bei4.utils.cjs.js +1051 -250
  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 +1047 -251
  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 +1051 -250
  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 +281 -0
  20. package/dist/audio.cjs.map +1 -0
  21. package/dist/audio.js +278 -0
  22. package/dist/audio.js.map +1 -0
  23. package/dist/common.cjs +6 -6
  24. package/dist/common.cjs.map +1 -1
  25. package/dist/common.js +6 -6
  26. package/dist/common.js.map +1 -1
  27. package/dist/download.cjs +43 -0
  28. package/dist/download.cjs.map +1 -1
  29. package/dist/download.js +43 -1
  30. package/dist/download.js.map +1 -1
  31. package/dist/evt.cjs +148 -148
  32. package/dist/evt.cjs.map +1 -1
  33. package/dist/evt.js +148 -148
  34. package/dist/evt.js.map +1 -1
  35. package/dist/id.cjs +68 -68
  36. package/dist/id.cjs.map +1 -1
  37. package/dist/id.js +68 -68
  38. package/dist/id.js.map +1 -1
  39. package/dist/timer.cjs +0 -1
  40. package/dist/timer.cjs.map +1 -1
  41. package/dist/timer.js +0 -1
  42. package/dist/timer.js.map +1 -1
  43. package/dist/tree.cjs +75 -0
  44. package/dist/tree.cjs.map +1 -1
  45. package/dist/tree.js +75 -1
  46. package/dist/tree.js.map +1 -1
  47. package/dist/webSocket.cjs +409 -0
  48. package/dist/webSocket.cjs.map +1 -0
  49. package/dist/webSocket.js +407 -0
  50. package/dist/webSocket.js.map +1 -0
  51. package/package.json +11 -1
  52. package/readme.txt +8 -5
  53. package/types/audio.d.ts +57 -0
  54. package/types/download.d.ts +12 -1
  55. package/types/index.d.ts +207 -1
  56. package/types/tree.d.ts +17 -1
  57. package/types/webSocket.d.ts +124 -0
@@ -0,0 +1,407 @@
1
+ /**
2
+ * @class WebSocketManager - 一个功能强大、高度可定制的 WebSocket 助手类
3
+ *
4
+ * @features
5
+ * - 智能断线重连 (指数退避算法)
6
+ * - 网络状态监听
7
+ * - 高度可定制的心跳保活机制 (通过注入函数实现)
8
+ * - 页面可见性 API 集成
9
+ * - 消息发送队列
10
+ * - 事件驱动的消息分发
11
+ * - 清晰的生命周期管理
12
+ * - 优雅的资源销毁
13
+ * - 可配置的数据序列化/反序列化
14
+ */
15
+ class WebSocketManager {
16
+ /**
17
+ * @param {string} url - WebSocket 服务器的地址
18
+ * @param {object} [options={}] - 配置选项
19
+ * @param {number} [options.heartbeatInterval=30000] - 心跳间隔 (毫秒)
20
+ * @param {number} [options.heartbeatTimeout=10000] - 心跳超时时间 (毫秒)
21
+ * @param {number} [options.reconnectBaseInterval=1000] - 重连基础间隔 (毫秒)
22
+ * @param {number} [options.maxReconnectInterval=30000] - 最大重连间隔 (毫秒)
23
+ * @param {number} [options.maxReconnectAttempts=Infinity] - 最大重连次数
24
+ * @param {boolean} [options.autoConnect=true] - 是否在实例化后自动连接
25
+ * @param {boolean} [options.serializeData=false] - 发送数据时是否自动序列化为JSON字符串
26
+ * @param {boolean} [options.deserializeData=false] - 接收数据时是否自动反序列化为JSON对象
27
+ * @param {function|null} [options.getPingMessage=null] - 返回要发送的 ping 消息内容的函数。如果为 null,则不发送心跳。
28
+ * @param {function|null} [options.isPongMessage=null] - 判断接收到的消息是否为 pong 的函数。接收 MessageEvent 对象作为参数,返回布尔值。
29
+ * @param {object} [options.protocols] - WebSocket 协议
30
+ */
31
+ constructor(url, options = {}) {
32
+ this.url = url;
33
+
34
+ // 默认的心跳实现,用于向后兼容
35
+ const defaultGetPingMessage = () => JSON.stringify({ type: "ping" });
36
+ const defaultIsPongMessage = (event) => {
37
+ try {
38
+ const message = JSON.parse(event.data);
39
+ return message.type === "pong";
40
+ } catch (e) {
41
+ return false;
42
+ }
43
+ };
44
+
45
+ this.options = {
46
+ heartbeatInterval: 30000,
47
+ heartbeatTimeout: 10000,
48
+ reconnectBaseInterval: 1000,
49
+ maxReconnectInterval: 30000,
50
+ maxReconnectAttempts: Infinity,
51
+ autoConnect: true,
52
+ serializeData: false,
53
+ deserializeData: false,
54
+ getPingMessage: defaultGetPingMessage, // 默认提供 ping 消息生成器
55
+ isPongMessage: defaultIsPongMessage, // 默认提供 pong 消息判断器
56
+ ...options
57
+ };
58
+
59
+ // WebSocket 实例
60
+ this.ws = null;
61
+
62
+ // 状态管理
63
+ this.readyState = WebSocket.CLOSED;
64
+ this.reconnectAttempts = 0;
65
+ this.forcedClose = false;
66
+ this.isReconnecting = false;
67
+
68
+ // 定时器
69
+ this.heartbeatTimer = null;
70
+ this.heartbeatTimeoutTimer = null;
71
+ this.reconnectTimer = null;
72
+
73
+ // 消息队列
74
+ this.messageQueue = [];
75
+
76
+ // 事件监听器
77
+ this.listeners = new Map();
78
+
79
+ // 绑定方法上下文
80
+ this._onOpen = this._onOpen.bind(this);
81
+ this._onMessage = this._onMessage.bind(this);
82
+ this._onClose = this._onClose.bind(this);
83
+ this._onError = this._onError.bind(this);
84
+ this._handleVisibilityChange = this._handleVisibilityChange.bind(this);
85
+ this._handleOnline = this._handleOnline.bind(this);
86
+ this._handleOffline = this._handleOffline.bind(this);
87
+
88
+ this._setupEventListeners();
89
+
90
+ if (this.options.autoConnect) {
91
+ this.connect();
92
+ }
93
+ }
94
+
95
+ // --- 公共 API ---
96
+
97
+ /**
98
+ * 连接到 WebSocket 服务器
99
+ */
100
+ connect() {
101
+ if (this.ws && (this.ws.readyState === WebSocket.CONNECTING || this.ws.readyState === WebSocket.OPEN)) {
102
+ return;
103
+ }
104
+
105
+ this.forcedClose = false;
106
+ this._updateReadyState(WebSocket.CONNECTING);
107
+ console.log(`[WS] 正在连接到 ${this.url}...`);
108
+ this._emit("connecting");
109
+
110
+ try {
111
+ this.ws = new WebSocket(this.url, this.options.protocols);
112
+ this.ws.onopen = this._onOpen;
113
+ this.ws.onmessage = this._onMessage;
114
+ this.ws.onclose = this._onClose;
115
+ this.ws.onerror = this._onError;
116
+ } catch (error) {
117
+ console.error("[WS] 连接失败:", error);
118
+ this._onError(error);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * 发送数据
124
+ * @param {string|object|ArrayBuffer|Blob} data - 要发送的数据
125
+ */
126
+ send(data) {
127
+ if (this.readyState === WebSocket.OPEN) {
128
+ let message;
129
+ // 根据配置决定是否序列化数据
130
+ if (this.options.serializeData) {
131
+ message = JSON.stringify(data);
132
+ } else {
133
+ message = data;
134
+ }
135
+ this.ws.send(message);
136
+ console.log("[WS] 消息已发送:", message);
137
+ } else {
138
+ console.warn("[WS] 连接未打开,消息已加入队列:", data);
139
+ this.messageQueue.push(data);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * 主动关闭连接
145
+ * @param {number} [code=1000] - 关闭代码
146
+ * @param {string} [reason=''] - 关闭原因
147
+ */
148
+ close(code = 1000, reason = "Normal closure") {
149
+ this.forcedClose = true;
150
+ this._stopHeartbeat();
151
+ this._clearReconnectTimer();
152
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
153
+ this.ws.close(code, reason);
154
+ } else {
155
+ this._updateReadyState(WebSocket.CLOSED);
156
+ this._emit("close", { code, reason, wasClean: true });
157
+ }
158
+ }
159
+
160
+ /**
161
+ * 彻底销毁实例,清理所有资源
162
+ */
163
+ destroy() {
164
+ console.log("[WS] 正在销毁实例...");
165
+ this.close(1000, "Instance destroyed");
166
+ this._removeEventListeners();
167
+ this.messageQueue = [];
168
+ this.listeners.clear();
169
+ this.ws = null;
170
+ }
171
+
172
+ /**
173
+ * 添加事件监听器
174
+ * @param {string} eventName - 事件名 (e.g., 'open', 'message', 'close', 'error', 'reconnect')
175
+ * @param {function} callback - 回调函数
176
+ */
177
+ on(eventName, callback) {
178
+ if (!this.listeners.has(eventName)) {
179
+ this.listeners.set(eventName, []);
180
+ }
181
+ this.listeners.get(eventName).push(callback);
182
+ }
183
+
184
+ /**
185
+ * 移除事件监听器
186
+ * @param {string} eventName - 事件名
187
+ * @param {function} callback - 回调函数
188
+ */
189
+ off(eventName, callback) {
190
+ if (this.listeners.has(eventName)) {
191
+ const callbacks = this.listeners.get(eventName);
192
+ const index = callbacks.indexOf(callback);
193
+ if (index > -1) {
194
+ callbacks.splice(index, 1);
195
+ }
196
+ }
197
+ }
198
+
199
+ // --- 内部方法 ---
200
+
201
+ _onOpen(event) {
202
+ console.log("[WS] 连接已建立");
203
+ this._updateReadyState(WebSocket.OPEN);
204
+ this.reconnectAttempts = 0;
205
+ this.isReconnecting = false;
206
+
207
+ // 如果配置了 getPingMessage,则启动心跳
208
+ if (this.options.getPingMessage) {
209
+ this._startHeartbeat();
210
+ }
211
+
212
+ this._flushMessageQueue();
213
+ this._emit("open", event);
214
+ }
215
+
216
+ /**
217
+ * 优化后的消息处理方法,使用注入的函数来判断心跳
218
+ * @param {MessageEvent} event
219
+ */
220
+ _onMessage(event) {
221
+ // --- 步骤 1: 使用注入的函数优先处理内部心跳机制 ---
222
+ if (this.options.isPongMessage && this.options.isPongMessage(event)) {
223
+ this._handlePong();
224
+ return; // 是心跳消息,处理完毕,直接返回
225
+ }
226
+
227
+ // --- 步骤 2: 根据用户配置处理业务消息 ---
228
+ if (this.options.deserializeData) {
229
+ try {
230
+ const parsedMessage = JSON.parse(event.data);
231
+ this._emit("message", parsedMessage);
232
+ } catch (e) {
233
+ this._emit("message", event.data);
234
+ }
235
+ } else {
236
+ this._emit("message", event.data);
237
+ }
238
+ }
239
+
240
+ _onClose(event) {
241
+ console.log("[WS] 连接已关闭", event);
242
+ this._updateReadyState(WebSocket.CLOSED);
243
+ this._stopHeartbeat();
244
+ this._emit("close", event);
245
+
246
+ if (!this.forcedClose) {
247
+ this._scheduleReconnect();
248
+ }
249
+ }
250
+
251
+ _onError(event) {
252
+ console.error("[WS] 连接发生错误:", event);
253
+ this._emit("error", event);
254
+ }
255
+
256
+ // --- 心跳机制 (重构) ---
257
+ _startHeartbeat() {
258
+ // 如果没有配置 getPingMessage,则无法启动心跳
259
+ if (!this.options.getPingMessage) {
260
+ return;
261
+ }
262
+
263
+ this._stopHeartbeat();
264
+ this.heartbeatTimer = setInterval(() => {
265
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
266
+ try {
267
+ // 调用注入的函数获取消息内容并发送
268
+ const pingMessage = this.options.getPingMessage();
269
+ this.ws.send(pingMessage);
270
+ console.log("[WS] 发送 Ping:", pingMessage);
271
+ this._setHeartbeatTimeout();
272
+ } catch (error) {
273
+ console.error("[WS] 发送 Ping 消息失败:", error);
274
+ }
275
+ }
276
+ }, this.options.heartbeatInterval);
277
+ }
278
+
279
+ _stopHeartbeat() {
280
+ if (this.heartbeatTimer) {
281
+ clearInterval(this.heartbeatTimer);
282
+ this.heartbeatTimer = null;
283
+ }
284
+ this._clearHeartbeatTimeout();
285
+ }
286
+
287
+ _setHeartbeatTimeout() {
288
+ this._clearHeartbeatTimeout();
289
+ this.heartbeatTimeoutTimer = setTimeout(() => {
290
+ console.error("[WS] 心跳超时,主动断开连接");
291
+ this.ws.close(1006, "Heartbeat timeout");
292
+ }, this.options.heartbeatTimeout);
293
+ }
294
+
295
+ _clearHeartbeatTimeout() {
296
+ if (this.heartbeatTimeoutTimer) {
297
+ clearTimeout(this.heartbeatTimeoutTimer);
298
+ this.heartbeatTimeoutTimer = null;
299
+ }
300
+ }
301
+
302
+ _handlePong() {
303
+ console.log("[WS] 收到 Pong");
304
+ this._clearHeartbeatTimeout();
305
+ }
306
+
307
+ // --- 重连机制 ---
308
+ _scheduleReconnect() {
309
+ if (this.forcedClose || this.isReconnecting || this.reconnectAttempts >= this.options.maxReconnectAttempts) {
310
+ if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
311
+ console.error("[WS] 已达到最大重连次数,停止重连");
312
+ this._emit("reconnect-failed");
313
+ }
314
+ return;
315
+ }
316
+
317
+ this.isReconnecting = true;
318
+ const interval = Math.min(this.options.reconnectBaseInterval * Math.pow(2, this.reconnectAttempts), this.options.maxReconnectInterval);
319
+
320
+ console.log(`[WS] ${interval / 1000}秒后将尝试第 ${this.reconnectAttempts + 1} 次重连...`);
321
+ this._emit("reconnect-attempt", { attempt: this.reconnectAttempts + 1, interval });
322
+
323
+ this.reconnectTimer = setTimeout(() => {
324
+ this.reconnectAttempts++;
325
+ this.connect();
326
+ }, interval);
327
+ }
328
+
329
+ _clearReconnectTimer() {
330
+ if (this.reconnectTimer) {
331
+ clearTimeout(this.reconnectTimer);
332
+ this.reconnectTimer = null;
333
+ }
334
+ }
335
+
336
+ // --- 消息队列 ---
337
+ _flushMessageQueue() {
338
+ if (this.messageQueue.length === 0) return;
339
+ console.log(`[WS] 发送队列中的 ${this.messageQueue.length} 条消息`);
340
+ const queue = [...this.messageQueue];
341
+ this.messageQueue = [];
342
+ queue.forEach((data) => this.send(data));
343
+ }
344
+
345
+ // --- 事件系统 ---
346
+ _emit(eventName, ...args) {
347
+ if (this.listeners.has(eventName)) {
348
+ this.listeners.get(eventName).forEach((callback) => callback(...args));
349
+ }
350
+ }
351
+
352
+ // --- 状态与监听器管理 ---
353
+ _updateReadyState(newState) {
354
+ this.readyState = newState;
355
+ this._emit("ready-state-change", newState);
356
+ }
357
+
358
+ _setupEventListeners() {
359
+ document.addEventListener("visibilitychange", this._handleVisibilityChange);
360
+ window.addEventListener("online", this._handleOnline);
361
+ window.addEventListener("offline", this._handleOffline);
362
+ }
363
+
364
+ _removeEventListeners() {
365
+ document.removeEventListener("visibilitychange", this._handleVisibilityChange);
366
+ window.removeEventListener("online", this._handleOnline);
367
+ window.removeEventListener("offline", this._handleOffline);
368
+ }
369
+
370
+ _handleVisibilityChange() {
371
+ // 如果未启用心跳,不需要处理页面可见性变化
372
+ if (!this.options.enableHeartbeat) {
373
+ return;
374
+ }
375
+
376
+ if (document.hidden) {
377
+ console.log("[WS] 页面隐藏,停止心跳");
378
+ this._stopHeartbeat();
379
+ } else {
380
+ console.log("[WS] 页面可见,检查连接状态");
381
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
382
+ this._startHeartbeat();
383
+ } else if (!this.forcedClose && !this.isReconnecting) {
384
+ this.connect();
385
+ }
386
+ }
387
+ }
388
+
389
+ _handleOnline() {
390
+ console.log("[WS] 网络已恢复,尝试重连");
391
+ if (!this.forcedClose && this.readyState !== WebSocket.OPEN) {
392
+ this._clearReconnectTimer(); // 清除当前的重连计划
393
+ this.connect(); // 立即尝试连接
394
+ }
395
+ }
396
+
397
+ _handleOffline() {
398
+ console.log("[WS] 网络已断开");
399
+ this._clearReconnectTimer(); // 停止重连尝试
400
+ // ws.onclose 会被触发,从而启动重连逻辑,但我们已经停止了
401
+ // 所以这里可以手动触发一次 close 事件来通知应用层
402
+ if (this.ws) this.ws.onclose({ code: 1006, reason: "Network offline" });
403
+ }
404
+ }
405
+
406
+ export { WebSocketManager };
407
+ //# sourceMappingURL=webSocket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webSocket.js","sources":["../src/source/webSocket.js"],"sourcesContent":["/**\n * @class WebSocketManager - 一个功能强大、高度可定制的 WebSocket 助手类\n *\n * @features\n * - 智能断线重连 (指数退避算法)\n * - 网络状态监听\n * - 高度可定制的心跳保活机制 (通过注入函数实现)\n * - 页面可见性 API 集成\n * - 消息发送队列\n * - 事件驱动的消息分发\n * - 清晰的生命周期管理\n * - 优雅的资源销毁\n * - 可配置的数据序列化/反序列化\n */\nexport class WebSocketManager {\n /**\n * @param {string} url - WebSocket 服务器的地址\n * @param {object} [options={}] - 配置选项\n * @param {number} [options.heartbeatInterval=30000] - 心跳间隔 (毫秒)\n * @param {number} [options.heartbeatTimeout=10000] - 心跳超时时间 (毫秒)\n * @param {number} [options.reconnectBaseInterval=1000] - 重连基础间隔 (毫秒)\n * @param {number} [options.maxReconnectInterval=30000] - 最大重连间隔 (毫秒)\n * @param {number} [options.maxReconnectAttempts=Infinity] - 最大重连次数\n * @param {boolean} [options.autoConnect=true] - 是否在实例化后自动连接\n * @param {boolean} [options.serializeData=false] - 发送数据时是否自动序列化为JSON字符串\n * @param {boolean} [options.deserializeData=false] - 接收数据时是否自动反序列化为JSON对象\n * @param {function|null} [options.getPingMessage=null] - 返回要发送的 ping 消息内容的函数。如果为 null,则不发送心跳。\n * @param {function|null} [options.isPongMessage=null] - 判断接收到的消息是否为 pong 的函数。接收 MessageEvent 对象作为参数,返回布尔值。\n * @param {object} [options.protocols] - WebSocket 协议\n */\n constructor(url, options = {}) {\n this.url = url;\n\n // 默认的心跳实现,用于向后兼容\n const defaultGetPingMessage = () => JSON.stringify({ type: \"ping\" });\n const defaultIsPongMessage = (event) => {\n try {\n const message = JSON.parse(event.data);\n return message.type === \"pong\";\n } catch (e) {\n return false;\n }\n };\n\n this.options = {\n heartbeatInterval: 30000,\n heartbeatTimeout: 10000,\n reconnectBaseInterval: 1000,\n maxReconnectInterval: 30000,\n maxReconnectAttempts: Infinity,\n autoConnect: true,\n serializeData: false,\n deserializeData: false,\n getPingMessage: defaultGetPingMessage, // 默认提供 ping 消息生成器\n isPongMessage: defaultIsPongMessage, // 默认提供 pong 消息判断器\n ...options\n };\n\n // WebSocket 实例\n this.ws = null;\n\n // 状态管理\n this.readyState = WebSocket.CLOSED;\n this.reconnectAttempts = 0;\n this.forcedClose = false;\n this.isReconnecting = false;\n\n // 定时器\n this.heartbeatTimer = null;\n this.heartbeatTimeoutTimer = null;\n this.reconnectTimer = null;\n\n // 消息队列\n this.messageQueue = [];\n\n // 事件监听器\n this.listeners = new Map();\n\n // 绑定方法上下文\n this._onOpen = this._onOpen.bind(this);\n this._onMessage = this._onMessage.bind(this);\n this._onClose = this._onClose.bind(this);\n this._onError = this._onError.bind(this);\n this._handleVisibilityChange = this._handleVisibilityChange.bind(this);\n this._handleOnline = this._handleOnline.bind(this);\n this._handleOffline = this._handleOffline.bind(this);\n\n this._setupEventListeners();\n\n if (this.options.autoConnect) {\n this.connect();\n }\n }\n\n // --- 公共 API ---\n\n /**\n * 连接到 WebSocket 服务器\n */\n connect() {\n if (this.ws && (this.ws.readyState === WebSocket.CONNECTING || this.ws.readyState === WebSocket.OPEN)) {\n return;\n }\n\n this.forcedClose = false;\n this._updateReadyState(WebSocket.CONNECTING);\n console.log(`[WS] 正在连接到 ${this.url}...`);\n this._emit(\"connecting\");\n\n try {\n this.ws = new WebSocket(this.url, this.options.protocols);\n this.ws.onopen = this._onOpen;\n this.ws.onmessage = this._onMessage;\n this.ws.onclose = this._onClose;\n this.ws.onerror = this._onError;\n } catch (error) {\n console.error(\"[WS] 连接失败:\", error);\n this._onError(error);\n }\n }\n\n /**\n * 发送数据\n * @param {string|object|ArrayBuffer|Blob} data - 要发送的数据\n */\n send(data) {\n if (this.readyState === WebSocket.OPEN) {\n let message;\n // 根据配置决定是否序列化数据\n if (this.options.serializeData) {\n message = JSON.stringify(data);\n } else {\n message = data;\n }\n this.ws.send(message);\n console.log(\"[WS] 消息已发送:\", message);\n } else {\n console.warn(\"[WS] 连接未打开,消息已加入队列:\", data);\n this.messageQueue.push(data);\n }\n }\n\n /**\n * 主动关闭连接\n * @param {number} [code=1000] - 关闭代码\n * @param {string} [reason=''] - 关闭原因\n */\n close(code = 1000, reason = \"Normal closure\") {\n this.forcedClose = true;\n this._stopHeartbeat();\n this._clearReconnectTimer();\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.close(code, reason);\n } else {\n this._updateReadyState(WebSocket.CLOSED);\n this._emit(\"close\", { code, reason, wasClean: true });\n }\n }\n\n /**\n * 彻底销毁实例,清理所有资源\n */\n destroy() {\n console.log(\"[WS] 正在销毁实例...\");\n this.close(1000, \"Instance destroyed\");\n this._removeEventListeners();\n this.messageQueue = [];\n this.listeners.clear();\n this.ws = null;\n }\n\n /**\n * 添加事件监听器\n * @param {string} eventName - 事件名 (e.g., 'open', 'message', 'close', 'error', 'reconnect')\n * @param {function} callback - 回调函数\n */\n on(eventName, callback) {\n if (!this.listeners.has(eventName)) {\n this.listeners.set(eventName, []);\n }\n this.listeners.get(eventName).push(callback);\n }\n\n /**\n * 移除事件监听器\n * @param {string} eventName - 事件名\n * @param {function} callback - 回调函数\n */\n off(eventName, callback) {\n if (this.listeners.has(eventName)) {\n const callbacks = this.listeners.get(eventName);\n const index = callbacks.indexOf(callback);\n if (index > -1) {\n callbacks.splice(index, 1);\n }\n }\n }\n\n // --- 内部方法 ---\n\n _onOpen(event) {\n console.log(\"[WS] 连接已建立\");\n this._updateReadyState(WebSocket.OPEN);\n this.reconnectAttempts = 0;\n this.isReconnecting = false;\n\n // 如果配置了 getPingMessage,则启动心跳\n if (this.options.getPingMessage) {\n this._startHeartbeat();\n }\n\n this._flushMessageQueue();\n this._emit(\"open\", event);\n }\n\n /**\n * 优化后的消息处理方法,使用注入的函数来判断心跳\n * @param {MessageEvent} event\n */\n _onMessage(event) {\n // --- 步骤 1: 使用注入的函数优先处理内部心跳机制 ---\n if (this.options.isPongMessage && this.options.isPongMessage(event)) {\n this._handlePong();\n return; // 是心跳消息,处理完毕,直接返回\n }\n\n // --- 步骤 2: 根据用户配置处理业务消息 ---\n if (this.options.deserializeData) {\n try {\n const parsedMessage = JSON.parse(event.data);\n this._emit(\"message\", parsedMessage);\n } catch (e) {\n this._emit(\"message\", event.data);\n }\n } else {\n this._emit(\"message\", event.data);\n }\n }\n\n _onClose(event) {\n console.log(\"[WS] 连接已关闭\", event);\n this._updateReadyState(WebSocket.CLOSED);\n this._stopHeartbeat();\n this._emit(\"close\", event);\n\n if (!this.forcedClose) {\n this._scheduleReconnect();\n }\n }\n\n _onError(event) {\n console.error(\"[WS] 连接发生错误:\", event);\n this._emit(\"error\", event);\n }\n\n // --- 心跳机制 (重构) ---\n _startHeartbeat() {\n // 如果没有配置 getPingMessage,则无法启动心跳\n if (!this.options.getPingMessage) {\n return;\n }\n\n this._stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n try {\n // 调用注入的函数获取消息内容并发送\n const pingMessage = this.options.getPingMessage();\n this.ws.send(pingMessage);\n console.log(\"[WS] 发送 Ping:\", pingMessage);\n this._setHeartbeatTimeout();\n } catch (error) {\n console.error(\"[WS] 发送 Ping 消息失败:\", error);\n }\n }\n }, this.options.heartbeatInterval);\n }\n\n _stopHeartbeat() {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n this._clearHeartbeatTimeout();\n }\n\n _setHeartbeatTimeout() {\n this._clearHeartbeatTimeout();\n this.heartbeatTimeoutTimer = setTimeout(() => {\n console.error(\"[WS] 心跳超时,主动断开连接\");\n this.ws.close(1006, \"Heartbeat timeout\");\n }, this.options.heartbeatTimeout);\n }\n\n _clearHeartbeatTimeout() {\n if (this.heartbeatTimeoutTimer) {\n clearTimeout(this.heartbeatTimeoutTimer);\n this.heartbeatTimeoutTimer = null;\n }\n }\n\n _handlePong() {\n console.log(\"[WS] 收到 Pong\");\n this._clearHeartbeatTimeout();\n }\n\n // --- 重连机制 ---\n _scheduleReconnect() {\n if (this.forcedClose || this.isReconnecting || this.reconnectAttempts >= this.options.maxReconnectAttempts) {\n if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {\n console.error(\"[WS] 已达到最大重连次数,停止重连\");\n this._emit(\"reconnect-failed\");\n }\n return;\n }\n\n this.isReconnecting = true;\n const interval = Math.min(this.options.reconnectBaseInterval * Math.pow(2, this.reconnectAttempts), this.options.maxReconnectInterval);\n\n console.log(`[WS] ${interval / 1000}秒后将尝试第 ${this.reconnectAttempts + 1} 次重连...`);\n this._emit(\"reconnect-attempt\", { attempt: this.reconnectAttempts + 1, interval });\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectAttempts++;\n this.connect();\n }, interval);\n }\n\n _clearReconnectTimer() {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n\n // --- 消息队列 ---\n _flushMessageQueue() {\n if (this.messageQueue.length === 0) return;\n console.log(`[WS] 发送队列中的 ${this.messageQueue.length} 条消息`);\n const queue = [...this.messageQueue];\n this.messageQueue = [];\n queue.forEach((data) => this.send(data));\n }\n\n // --- 事件系统 ---\n _emit(eventName, ...args) {\n if (this.listeners.has(eventName)) {\n this.listeners.get(eventName).forEach((callback) => callback(...args));\n }\n }\n\n // --- 状态与监听器管理 ---\n _updateReadyState(newState) {\n this.readyState = newState;\n this._emit(\"ready-state-change\", newState);\n }\n\n _setupEventListeners() {\n document.addEventListener(\"visibilitychange\", this._handleVisibilityChange);\n window.addEventListener(\"online\", this._handleOnline);\n window.addEventListener(\"offline\", this._handleOffline);\n }\n\n _removeEventListeners() {\n document.removeEventListener(\"visibilitychange\", this._handleVisibilityChange);\n window.removeEventListener(\"online\", this._handleOnline);\n window.removeEventListener(\"offline\", this._handleOffline);\n }\n\n _handleVisibilityChange() {\n // 如果未启用心跳,不需要处理页面可见性变化\n if (!this.options.enableHeartbeat) {\n return;\n }\n\n if (document.hidden) {\n console.log(\"[WS] 页面隐藏,停止心跳\");\n this._stopHeartbeat();\n } else {\n console.log(\"[WS] 页面可见,检查连接状态\");\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this._startHeartbeat();\n } else if (!this.forcedClose && !this.isReconnecting) {\n this.connect();\n }\n }\n }\n\n _handleOnline() {\n console.log(\"[WS] 网络已恢复,尝试重连\");\n if (!this.forcedClose && this.readyState !== WebSocket.OPEN) {\n this._clearReconnectTimer(); // 清除当前的重连计划\n this.connect(); // 立即尝试连接\n }\n }\n\n _handleOffline() {\n console.log(\"[WS] 网络已断开\");\n this._clearReconnectTimer(); // 停止重连尝试\n // ws.onclose 会被触发,从而启动重连逻辑,但我们已经停止了\n // 所以这里可以手动触发一次 close 事件来通知应用层\n if (this.ws) this.ws.onclose({ code: 1006, reason: \"Network offline\" });\n }\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2bei4-utils",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "🧰 零依赖、ESM 的 JS 业务工具箱:数组乱序/深浅克隆、防抖节流、URL/query 解析、Date/时长格式化、随机汉字/字母、文件下载、事件总线、UUID/分布式短 ID、树结构互转等 40+ 常用函数 & 类,Tree-Shaking 友好。",
5
5
  "private": false,
6
6
  "type": "module",
@@ -20,6 +20,11 @@
20
20
  "import": "./dist/arr.js",
21
21
  "require": "./dist/arr.cjs"
22
22
  },
23
+ "./audio": {
24
+ "types": "./types/audio.d.ts",
25
+ "import": "./dist/audio.js",
26
+ "require": "./dist/audio.cjs"
27
+ },
23
28
  "./browser": {
24
29
  "types": "./types/browser.d.ts",
25
30
  "import": "./dist/browser.js",
@@ -59,6 +64,11 @@
59
64
  "types": "./types/tree.d.ts",
60
65
  "import": "./dist/tree.js",
61
66
  "require": "./dist/tree.cjs"
67
+ },
68
+ "./webSocket": {
69
+ "types": "./types/webSocket.d.ts",
70
+ "import": "./dist/webSocket.js",
71
+ "require": "./dist/webSocket.cjs"
62
72
  }
63
73
  },
64
74
  "files": [
package/readme.txt CHANGED
@@ -1,10 +1,13 @@
1
- 0、首次安装依赖
2
- pnpm i -D @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-terser prettier rollup rollup-plugin-dts rollup-plugin-filesize typescript
1
+ !!!写在最前面: dist 、 types 、 /src/temp 、 /src/index.js 是构建过程中生成的
3
2
 
4
-
5
- --> 官方 npm 仓库地址:https://registry.npmjs.org/
3
+ --> 官方 npm 仓库地址: https://registry.npmjs.org/
4
+ --> 阿里云 npm 仓库地址: https://registry.npmmirror.com/
6
5
  --> --registry 显示指定的仓库地址,如:http://192.168.x.x:4873
7
6
 
7
+
8
+ 0、首次安装依赖
9
+ pnpm i -D @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-terser prettier rollup rollup-plugin-dts rollup-plugin-filesize typescript
10
+
8
11
  1、如果没有用户,先注册用户
9
12
  pnpm adduser --registry https://registry.npmjs.org/
10
13
 
@@ -15,4 +18,4 @@ pnpm login --registry https://registry.npmjs.org/
15
18
  pnpm publish --registry https://registry.npmjs.org/
16
19
 
17
20
  4、撤销发布
18
- pnpm unpublish a2bei4-utils --force --registry https://registry.npmjs.org/
21
+ pnpm unpublish a2bei4-utils --force --registry https://registry.npmjs.org/
@@ -0,0 +1,57 @@
1
+ /**
2
+ * 将 16-bit 单声道 PCM 数据封装成标准 WAV Blob。
3
+ *
4
+ * @param {Int16Array} pcmData - PCM 采样数据
5
+ * @param {number} [sampleRate=16000] - 采样率,默认 16 kHz
6
+ * @returns {Blob} audio/wav Blob
7
+ */
8
+ declare function pcmToWavBlob(pcmData: Int16Array, sampleRate?: number): Blob;
9
+ /**
10
+ * 浏览器端实时音频流重采样器。
11
+ * 基于 AudioWorklet 将麦克风/媒体流转换为 16 kHz、16-bit、单声道 PCM,
12
+ * 并通过回调逐块输出,可选保存完整 PCM 用于后续合并。
13
+ */
14
+ declare class AudioStreamResampler {
15
+ /**
16
+ * @param {object} config
17
+ * @param {function(Int16Array)} config.onData - 收到一个 chunk PCM 数据的回调
18
+ * @param {function(string, string)} [config.onStateChange] - 状态变化回调
19
+ * @param {object} [config.processorOptions] - 传递给 AudioWorklet 的选项
20
+ * @param {boolean} [config.saveFullPcm=false] - 是否在内部保存所有 PCM 用于 stop 时合并(长时间录音建议关闭)
21
+ */
22
+ constructor(config: {
23
+ onData: (arg0: Int16Array) => any;
24
+ onStateChange?: ((arg0: string, arg1: string) => any) | undefined;
25
+ processorOptions?: object | undefined;
26
+ saveFullPcm?: boolean | undefined;
27
+ });
28
+ onData: (arg0: Int16Array) => any;
29
+ onStateChange: (arg0: string, arg1: string) => any;
30
+ processorOptions: object;
31
+ saveFullPcm: boolean;
32
+ audioContext: AudioContext | null;
33
+ workletNode: AudioWorkletNode | null;
34
+ source: MediaStreamAudioSourceNode | null;
35
+ workletUrl: string | null;
36
+ fullPcmData: never[] | null;
37
+ isInitialized: boolean;
38
+ isProcessing: boolean;
39
+ /**
40
+ * 初始化 AudioContext 并加载 AudioWorklet。
41
+ * 完成后状态变为 `"ready"`。
42
+ */
43
+ init(): Promise<void>;
44
+ /**
45
+ * 绑定媒体流,开始实时处理。
46
+ * @param {MediaStream} stream - 通过 getUserMedia 或其他方式获得的流
47
+ */
48
+ setMediaStream(stream: MediaStream): void;
49
+ /**
50
+ * 停止处理并释放资源。
51
+ * @param {(fullPcm: Int16Array) => void} [callback] - 若构造时 `saveFullPcm=true`,会把合并后的完整 PCM 通过此回调传出
52
+ */
53
+ stop(callback?: (fullPcm: Int16Array) => void): void;
54
+ _cleanup(): void;
55
+ }
56
+
57
+ export { AudioStreamResampler, pcmToWavBlob };
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * 通过动态创建 `<a>` 标签触发浏览器下载。
3
+ * 注意:此方法可能无法强制下载浏览器原生支持的文件(如图片、PDF),浏览器可能会选择直接打开。
3
4
  *
4
5
  * @param {string} url - 任意可下载地址(同源或允许跨域)
5
6
  * @param {string} [fileName] - 保存到本地的文件名;不传时使用时间戳
@@ -35,5 +36,15 @@ declare function downloadExcel(data: string | ArrayBufferView | ArrayBuffer | Bl
35
36
  * @param {string} [fileName] - 保存到本地的文件名
36
37
  */
37
38
  declare function downloadJSON(data: any, fileName?: string): void;
39
+ /**
40
+ * 通过 `fetch` 获取资源并强制下载,避免浏览器直接打开文件。
41
+ * 此方法适用于下载图片、PDF 等浏览器默认会打开的文件类型。
42
+ * 如果 `fetch` 失败(如 CORS 策略阻止),会回退到 `downloadByUrl` 方法作为备选方案。
43
+ *
44
+ * @param {string} url - 文件的 URL 地址
45
+ * @param {string} [fileName] - 保存到本地的文件名。如果不提供,会尝试从 URL 中自动提取
46
+ * @returns {Promise<void>} 返回一个 Promise,在下载开始或失败后 resolve
47
+ */
48
+ declare function fetchOrDownloadByUrl(url: string, fileName?: string): Promise<void>;
38
49
 
39
- export { downloadByBlob, downloadByData, downloadByUrl, downloadExcel, downloadJSON };
50
+ export { downloadByBlob, downloadByData, downloadByUrl, downloadExcel, downloadJSON, fetchOrDownloadByUrl };