zalo-toolkit 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.
@@ -0,0 +1,822 @@
1
+ 'use strict';
2
+
3
+ var stream = require('stream');
4
+ var DeliveredMessage = require('../models/DeliveredMessage.cjs');
5
+ var FriendEvent = require('../models/FriendEvent.cjs');
6
+ var GroupEvent = require('../models/GroupEvent.cjs');
7
+ var Listen = require('../models/Listen.cjs');
8
+ var Message = require('../models/Message.cjs');
9
+ var Reaction = require('../models/Reaction.cjs');
10
+ var SeenMessage = require('../models/SeenMessage.cjs');
11
+ var Typing = require('../models/Typing.cjs');
12
+ var Undo = require('../models/Undo.cjs');
13
+ var utils = require('../utils.cjs');
14
+ var configSocket = require('../socket/config-socket.cjs');
15
+ var ZaloApiError = require('../Errors/ZaloApiError.cjs');
16
+ var WebSocket = require('ws');
17
+ var lodash = require('lodash');
18
+
19
+ class Listener extends stream.EventEmitter {
20
+ constructor(api) {
21
+ super();
22
+ this.api = api;
23
+ this.id = 0;
24
+ this.retries = {};
25
+ this.retriesRound2 = {};
26
+ this.rotateCount = 0;
27
+ this.listUrlSocket = [];
28
+ this.pingHttp = {};
29
+ this.fibonacciRetry = new utils.FibonacciRetry();
30
+ this.configChanged = false;
31
+ this._onClose = (event) => {
32
+ console.log('Is closed now ', this.ctx.uid, event.code);
33
+ this.afterClose();
34
+ if (!event || event.code === null) {
35
+ this.handleError(Listen.ConnectionErrorCode.Unknown);
36
+ return;
37
+ }
38
+ if (event.code === Listen.CloseReason.AbnormalClosure) {
39
+ this.retry(Listen.NetworkErrorCode.CannotReachServerNum);
40
+ return;
41
+ }
42
+ if (event.code === Listen.CloseReason.NormalClosure) {
43
+ return;
44
+ }
45
+ if (event.code > 1000 && event.code <= 1015) {
46
+ this.retry(Listen.NetworkErrorCode.Internal);
47
+ return;
48
+ }
49
+ return this.handleError(event.code);
50
+ };
51
+ this.shouldCloseAndReconnect = (errorCode) => {
52
+ var _a, _b;
53
+ return !!((_b = (_a = this.settingSocket) === null || _a === void 0 ? void 0 : _a.close_and_retry_codes) === null || _b === void 0 ? void 0 : _b.includes(errorCode));
54
+ };
55
+ this.genNextReqId = () => {
56
+ return 'req_' + this.id++;
57
+ };
58
+ this.ctx = api.getContext();
59
+ if (!this.ctx.cookie)
60
+ throw new Error('Cookie is not available');
61
+ if (!this.ctx.userAgent)
62
+ throw new Error('User agent is not available');
63
+ this.listUrlSocket = api.wsUrls;
64
+ this.url = this.getSocketUrl(false, true);
65
+ this.cookie = this.ctx.cookie.getCookieStringSync('https://chat.zalo.me');
66
+ this.userAgent = this.ctx.userAgent;
67
+ this.selfListen = this.ctx.options.selfListen;
68
+ this.settingSocket = this.ctx.settings.features.socket;
69
+ this.onlineConfigs = this.ctx.settings.features.online_configs;
70
+ this.retries = this.ctx.settings.features.socket.retries;
71
+ this._configRetriesRound2();
72
+ this.ws = null;
73
+ this.onConnectedCallback = () => { };
74
+ this.onClosedCallback = () => { };
75
+ this.onErrorCallback = () => { };
76
+ this.onMessageCallback = () => { };
77
+ this.onGroupEventCallback = () => { };
78
+ this.onMuteCallback = () => { };
79
+ this.onReactionCallback = () => { };
80
+ this.onOldMessageCallback = () => { };
81
+ this.onUndoMessageCallback = () => { };
82
+ this.onUpdateQueueStatusCallback = () => { };
83
+ this.onLabelEventCallback = () => { };
84
+ this.onFriendEventCallback = () => { };
85
+ }
86
+ onConnected(cb) {
87
+ this.onConnectedCallback = cb;
88
+ }
89
+ onClosed(cb) {
90
+ this.onClosedCallback = cb;
91
+ }
92
+ onError(cb) {
93
+ this.onErrorCallback = cb;
94
+ }
95
+ onMessage(cb) {
96
+ this.onMessageCallback = cb;
97
+ }
98
+ onOldMessage(cb) {
99
+ this.onOldMessageCallback = cb;
100
+ }
101
+ onGroupEvent(cb) {
102
+ this.onGroupEventCallback = cb;
103
+ }
104
+ onMute(cb) {
105
+ this.onMuteCallback = cb;
106
+ }
107
+ onReaction(cb) {
108
+ this.onReactionCallback = cb;
109
+ }
110
+ onUndo(cb) {
111
+ this.onUndoMessageCallback = cb;
112
+ }
113
+ onUpdateQueueStatus(cb) {
114
+ this.onUpdateQueueStatusCallback = cb;
115
+ }
116
+ onLabelEvent(cb) {
117
+ this.onLabelEventCallback = cb;
118
+ }
119
+ onFriendEvent(cb) {
120
+ this.onFriendEventCallback = cb;
121
+ }
122
+ start() {
123
+ if (this.ws)
124
+ throw new ZaloApiError.ZaloApiError('Already started');
125
+ const ws = new WebSocket(this.url, {
126
+ headers: {
127
+ 'accept-encoding': 'gzip, deflate, br, zstd',
128
+ 'accept-language': 'en-US,en;q=0.9',
129
+ 'cache-control': 'no-cache',
130
+ connection: 'Upgrade',
131
+ host: new URL(this.url).host,
132
+ origin: 'https://chat.zalo.me',
133
+ prgama: 'no-cache',
134
+ 'sec-websocket-extensions': 'permessage-deflate; client_max_window_bits',
135
+ 'sec-websocket-version': '13',
136
+ upgrade: 'websocket',
137
+ 'user-agent': this.userAgent,
138
+ cookie: this.cookie,
139
+ },
140
+ });
141
+ this.ws = ws;
142
+ this.configSocket = new configSocket.ConfigSocket(ws, this.ctx.settings.features, this.ctx, this);
143
+ ws.onopen = () => {
144
+ this.onConnectedCallback();
145
+ this.emit('connected');
146
+ };
147
+ ws.onclose = this._onClose;
148
+ ws.onerror = (event) => {
149
+ // console.log('event error: ', this.ctx.uid, event);
150
+ };
151
+ ws.onmessage = async (event) => {
152
+ var _a, _b, _c;
153
+ const { data } = event;
154
+ if (!(data instanceof Buffer))
155
+ return;
156
+ const encodedHeader = data.subarray(0, 4);
157
+ const [version, cmd, subCmd] = getHeader(encodedHeader);
158
+ try {
159
+ const dataToDecode = data.subarray(4);
160
+ const decodedData = new TextDecoder('utf-8').decode(dataToDecode);
161
+ if (decodedData.length == 0)
162
+ return;
163
+ const parsed = JSON.parse(decodedData);
164
+ if (cmd === Listen.MessageCommand.Ping) {
165
+ if (subCmd === Listen.KeepAliveCommand.KeepConnection || this.onlineConfigs.send_active_to_keep_live) {
166
+ if ((_a = this.settingSocket.debug) === null || _a === void 0 ? void 0 : _a.skip_ping) {
167
+ return;
168
+ }
169
+ this.pong(parsed);
170
+ }
171
+ if (parsed && !isNaN(parsed.status)) {
172
+ this.emit(Listen.SocketEvent.ON_PING_ACTIVE_KEEP_ALIVE, parsed);
173
+ }
174
+ }
175
+ if (version == 1 && cmd == 1 && subCmd == 1 && parsed.hasOwnProperty('key')) {
176
+ this.cipherKey = parsed.key;
177
+ this.emit('cipher_key', parsed.key);
178
+ this.clearPingTimer();
179
+ this.createPingTimerServer();
180
+ if (this.pingInterval)
181
+ clearInterval(this.pingInterval);
182
+ this.pingInterval = setInterval(() => {
183
+ this.ping();
184
+ }, 3 * 60 * 1000);
185
+ }
186
+ if (version == 1 && cmd == 501 && subCmd == 0) {
187
+ const parsedData = (await utils.decodeEventData(parsed, this.cipherKey)).data;
188
+ const { msgs, queueStatus } = parsedData;
189
+ const listUndo = [];
190
+ const listMessages = [];
191
+ for (const msg of msgs) {
192
+ if (typeof msg.content == 'object' && msg.content.hasOwnProperty('deleteMsg')) {
193
+ const undoObject = new Undo.Undo(this.ctx.uid, msg, false);
194
+ if (undoObject.isSelf && !this.selfListen)
195
+ continue;
196
+ listUndo.push(undoObject);
197
+ this.emit('undo', undoObject);
198
+ }
199
+ else {
200
+ const messageObject = new Message.UserMessage(this.ctx.uid, msg, queueStatus);
201
+ if (messageObject.isSelf && !this.selfListen)
202
+ continue;
203
+ listMessages.push(messageObject);
204
+ this.emit('message', messageObject);
205
+ }
206
+ }
207
+ const lastMsg = lodash.last(msgs);
208
+ const timestamp = lastMsg ? Number(lastMsg.ts) : Date.now();
209
+ this.onUndoMessageCallback(listUndo);
210
+ this.onMessageCallback(listMessages);
211
+ this.onUpdateQueueStatusCallback(queueStatus, timestamp);
212
+ }
213
+ if (version == 1 && cmd == 521 && subCmd == 0) {
214
+ const parsedData = (await utils.decodeEventData(parsed, this.cipherKey)).data;
215
+ const { groupMsgs, queueStatus } = parsedData;
216
+ const listUndo = [];
217
+ const listMessages = [];
218
+ for (const msg of groupMsgs) {
219
+ if (typeof msg.content == 'object' && msg.content.hasOwnProperty('deleteMsg')) {
220
+ const undoObject = new Undo.Undo(this.ctx.uid, msg, true);
221
+ if (undoObject.isSelf && !this.selfListen)
222
+ continue;
223
+ listUndo.push(undoObject);
224
+ this.emit('undo', undoObject);
225
+ }
226
+ else {
227
+ const messageObject = new Message.GroupMessage(this.ctx.uid, msg, queueStatus);
228
+ if (messageObject.isSelf && !this.selfListen)
229
+ continue;
230
+ listMessages.push(messageObject);
231
+ this.emit('message', messageObject);
232
+ }
233
+ }
234
+ const lastMsg = lodash.last(groupMsgs);
235
+ const timestamp = lastMsg ? Number(lastMsg.ts) : Date.now();
236
+ this.onUndoMessageCallback(listUndo);
237
+ this.onMessageCallback(listMessages);
238
+ this.onUpdateQueueStatusCallback(queueStatus, timestamp);
239
+ }
240
+ if ([510, 511, 610, 611, 603, 604].includes(cmd)) {
241
+ const parsedData = (await utils.decodeEventData(parsed, this.cipherKey)).data;
242
+ this.configSocket.onGotOffline(cmd, subCmd, { data: parsedData });
243
+ }
244
+ if (version == 1 && [601, 603, 604].includes(cmd)) {
245
+ const parsedData = (await utils.decodeEventData(parsed, this.cipherKey)).data;
246
+ const { controls, queueStatus } = parsedData;
247
+ const listGroupEvents = [];
248
+ const listMuteEvents = [];
249
+ const listFriendEvents = [];
250
+ let hasLabelEvent = false;
251
+ for (const control of controls) {
252
+ if (control.content.act_type == 'file_done') {
253
+ const data = {
254
+ fileUrl: control.content.data.url,
255
+ fileId: control.content.fileId,
256
+ };
257
+ const callbackId = `${this.ctx.uid}_${control.content.fileId}`;
258
+ (_c = (_b = this.api).uploadCompleted) === null || _c === void 0 ? void 0 : _c.call(_b, { callbackId, data });
259
+ this.emit('upload_attachment', data);
260
+ }
261
+ else if (control.content.act_type == 'group') {
262
+ // 31/08/2024
263
+ // for some reason, Zalo send both join and join_reject event when admin approve join requests
264
+ // Zalo itself doesn't seem to handle this properly either, so we gonna ignore the join_reject event
265
+ if (control.content.act == 'join_reject')
266
+ continue;
267
+ const groupEventData = typeof control.content.data == 'string' ? JSON.parse(control.content.data) : control.content.data;
268
+ const groupEvent = GroupEvent.initializeGroupEvent(this.ctx.uid, groupEventData, utils.getGroupEventType(control.content.act), control.content.act);
269
+ const { data } = groupEvent;
270
+ if (groupEvent.isSelf && !this.selfListen)
271
+ continue;
272
+ const timestamp = parseInt(data['time']) || Date.now();
273
+ this.onUpdateQueueStatusCallback(queueStatus, timestamp);
274
+ listGroupEvents.push(groupEvent);
275
+ this.emit('group_event', groupEvent);
276
+ }
277
+ else if (control.content.act_type == 'fr') {
278
+ // 28/02/2025
279
+ // Zalo send both req and req_v2 event when user send friend request
280
+ // Zalo itself doesn't seem to handle this properly either, so we gonna ignore the req event
281
+ if (control.content.act == 'req' || control.content.act == 'seen_fr_req')
282
+ continue;
283
+ const friendEventData = typeof control.content.data === 'string' && utils.looksLikeJson(control.content.data) ? JSON.parse(control.content.data) : control.content.data;
284
+ // Handles the case when act is "pin_create" and params is a string
285
+ if (typeof friendEventData == 'object' && 'topic' in friendEventData && typeof friendEventData.topic == 'object' && 'params' in friendEventData.topic) {
286
+ friendEventData.topic.params = JSON.parse(`${friendEventData.topic.params}`);
287
+ }
288
+ const friendEvent = FriendEvent.initializeFriendEvent(this.ctx.uid, friendEventData, utils.getFriendEventType(control.content.act));
289
+ if (friendEvent.isSelf && !this.selfListen)
290
+ continue;
291
+ listFriendEvents.push(friendEvent);
292
+ }
293
+ else if (control.content.act_type == 'mute') {
294
+ const muteData = typeof control.content.data == 'string' && utils.looksLikeJson(control.content.data) ? JSON.parse(control.content.data) : control.content.data;
295
+ const { systemTime } = muteData;
296
+ listMuteEvents.push({ data: muteData, act: control.content.act });
297
+ if (systemTime) {
298
+ this.onUpdateQueueStatusCallback(queueStatus, systemTime);
299
+ }
300
+ }
301
+ else if (control.content.act_type == 'label_convers') {
302
+ hasLabelEvent = true;
303
+ }
304
+ }
305
+ this.onGroupEventCallback(listGroupEvents);
306
+ this.onMuteCallback(listMuteEvents);
307
+ this.onFriendEventCallback(listFriendEvents);
308
+ if (hasLabelEvent) {
309
+ this.onLabelEventCallback();
310
+ }
311
+ }
312
+ if ([610, 611, 612].includes(cmd)) {
313
+ const parsedData = (await utils.decodeEventData(parsed, this.cipherKey)).data;
314
+ const { reacts, reactGroups, queueStatus } = parsedData;
315
+ const listReactions = [];
316
+ for (const react of reacts) {
317
+ react.content = JSON.parse(react.content);
318
+ const reactionObject = new Reaction.Reaction(this.ctx.uid, react, false);
319
+ if (reactionObject.isSelf && !this.selfListen)
320
+ continue;
321
+ listReactions.push(reactionObject);
322
+ this.emit('reaction', reactionObject);
323
+ }
324
+ for (const reactGroup of reactGroups) {
325
+ reactGroup.content = JSON.parse(reactGroup.content);
326
+ const reactionObject = new Reaction.Reaction(this.ctx.uid, reactGroup, true);
327
+ if (reactionObject.isSelf && !this.selfListen)
328
+ continue;
329
+ listReactions.push(reactionObject);
330
+ this.emit('reaction', reactionObject);
331
+ }
332
+ const lastMsg = reacts.length > 0 ? lodash.last(reacts) : reactGroups.length > 0 ? lodash.last(reactGroups) : null;
333
+ const timestamp = lastMsg ? Number(lastMsg.ts) : Date.now();
334
+ this.onReactionCallback(listReactions);
335
+ this.onUpdateQueueStatusCallback(queueStatus, timestamp);
336
+ }
337
+ if (cmd == 510) {
338
+ const parsedData = (await utils.decodeEventData(parsed, this.cipherKey)).data;
339
+ const { msgs, queueStatus } = parsedData;
340
+ const responseUndos = [];
341
+ const responseMsgs = [];
342
+ msgs.forEach((msg) => {
343
+ if (typeof msg.content == 'object' && msg.content.hasOwnProperty('deleteMsg')) {
344
+ const undoObject = new Undo.Undo(this.ctx.uid, msg, false);
345
+ responseUndos.push(undoObject);
346
+ }
347
+ else {
348
+ const messageObject = new Message.UserMessage(this.ctx.uid, msg, queueStatus);
349
+ responseMsgs.push(messageObject);
350
+ }
351
+ });
352
+ const lastMsg = lodash.last(msgs);
353
+ const timestamp = lastMsg ? Number(lastMsg.ts) : Date.now();
354
+ this.onUpdateQueueStatusCallback(queueStatus, timestamp);
355
+ this.onOldMessageCallback(responseMsgs);
356
+ this.onUndoMessageCallback(responseUndos);
357
+ this.emit('old_messages', responseMsgs);
358
+ }
359
+ if (cmd == 511) {
360
+ const parsedData = (await utils.decodeEventData(parsed, this.cipherKey)).data;
361
+ const { groupMsgs, queueStatus } = parsedData;
362
+ const responseUndos = [];
363
+ const responseMsgs = [];
364
+ for (const msg of groupMsgs) {
365
+ if (typeof msg.content == 'object' && msg.content.hasOwnProperty('deleteMsg')) {
366
+ const undoObject = new Undo.Undo(this.ctx.uid, msg, true);
367
+ responseUndos.push(undoObject);
368
+ }
369
+ else {
370
+ const messageObject = new Message.GroupMessage(this.ctx.uid, msg, queueStatus);
371
+ responseMsgs.push(messageObject);
372
+ }
373
+ }
374
+ const lastMsg = lodash.last(groupMsgs);
375
+ const timestamp = lastMsg ? Number(lastMsg.ts) : Date.now();
376
+ this.onUpdateQueueStatusCallback(queueStatus, timestamp);
377
+ this.onOldMessageCallback(responseMsgs);
378
+ this.onUndoMessageCallback(responseUndos);
379
+ this.emit('old_messages', responseMsgs);
380
+ }
381
+ if (cmd == 518) {
382
+ const parsedData = (await utils.decodeEventData(parsed, this.cipherKey)).data;
383
+ const { msgs, groupMsgs, queueStatus } = parsedData;
384
+ const parseMsgs = msgs.map((msg) => new Message.UserMessage(this.ctx.uid, msg, queueStatus));
385
+ const parseGroupMsgs = groupMsgs.map((msg) => new Message.GroupMessage(this.ctx.uid, msg, queueStatus));
386
+ const listMessage = [...parseMsgs, ...parseGroupMsgs];
387
+ const lastMsg = lodash.last(listMessage);
388
+ const timestamp = lastMsg ? Number(lastMsg.ts) : Date.now();
389
+ this.onUpdateQueueStatusCallback(queueStatus, timestamp);
390
+ this.onOldMessageCallback(listMessage);
391
+ this.emit('old_messages', listMessage);
392
+ }
393
+ if (cmd == 602 && subCmd == 0) {
394
+ const parsedData = (await utils.decodeEventData(parsed, this.cipherKey)).data;
395
+ const { actions } = parsedData;
396
+ for (const action of actions) {
397
+ const data = JSON.parse(`{${action.data}}`);
398
+ if (action.act_type == 'typing') {
399
+ if (action.act == 'typing') {
400
+ const typingObject = new Typing.UserTyping(data);
401
+ this.emit('typing', typingObject);
402
+ }
403
+ else if (action.act == 'gtyping') {
404
+ // 26/02/2025
405
+ // For a group with only two people, Zalo doesn't send a typing event.
406
+ const typingObject = new Typing.GroupTyping(data);
407
+ this.emit('typing', typingObject);
408
+ }
409
+ }
410
+ }
411
+ }
412
+ if (cmd == 502 && subCmd == 0) {
413
+ const parsedData = (await utils.decodeEventData(parsed, this.cipherKey)).data;
414
+ const { delivereds: deliveredMsgs, seens: seenMsgs } = parsedData;
415
+ if (Array.isArray(deliveredMsgs) && deliveredMsgs.length > 0) {
416
+ const deliveredObjects = deliveredMsgs.map((delivered) => new DeliveredMessage.UserDeliveredMessage(delivered));
417
+ this.emit('delivered_messages', deliveredObjects);
418
+ }
419
+ if (Array.isArray(seenMsgs) && seenMsgs.length > 0) {
420
+ const seenObjects = seenMsgs.map((seen) => new SeenMessage.UserSeenMessage(seen));
421
+ this.emit('seen_messages', seenObjects);
422
+ }
423
+ }
424
+ if (cmd == 522 && subCmd == 0) {
425
+ const parsedData = (await utils.decodeEventData(parsed, this.cipherKey)).data;
426
+ const { delivereds: deliveredMsgs, groupSeens: groupSeenMsgs } = parsedData;
427
+ if (Array.isArray(deliveredMsgs) && deliveredMsgs.length > 0) {
428
+ let deliveredObjects = deliveredMsgs.map((delivered) => new DeliveredMessage.GroupDeliveredMessage(this.ctx.uid, delivered));
429
+ if (!this.selfListen)
430
+ deliveredObjects = deliveredObjects.filter((delivered) => !delivered.isSelf);
431
+ this.emit('delivered_messages', deliveredObjects);
432
+ }
433
+ if (Array.isArray(groupSeenMsgs) && groupSeenMsgs.length > 0) {
434
+ let seenObjects = groupSeenMsgs.map((seen) => new SeenMessage.GroupSeenMessage(this.ctx.uid, seen));
435
+ if (!this.selfListen)
436
+ seenObjects = seenObjects.filter((seen) => !seen.isSelf);
437
+ this.emit('seen_messages', seenObjects);
438
+ }
439
+ }
440
+ if (version == 1 && cmd == 3000 && subCmd == 0) {
441
+ console.log();
442
+ utils.logger(this.ctx).error('Another connection is opened, closing this one');
443
+ console.log();
444
+ if (ws.readyState !== WebSocket.CLOSED)
445
+ ws.close(Listen.CloseReason.NotAuthenticated);
446
+ }
447
+ }
448
+ catch (error) {
449
+ this.onErrorCallback(error);
450
+ }
451
+ };
452
+ }
453
+ stop() {
454
+ if (this.ws) {
455
+ this.ws.close(Listen.CloseReason.NormalClosure);
456
+ this.reset();
457
+ }
458
+ }
459
+ getSocketUrl(shouldRotate = false, forceFirstEndpoint = false) {
460
+ if (!this.listUrlSocket.length)
461
+ return '';
462
+ let index = 0;
463
+ if (shouldRotate) {
464
+ index = (this.rotateCount + 1) % this.listUrlSocket.length;
465
+ }
466
+ if (forceFirstEndpoint) {
467
+ index = 0;
468
+ }
469
+ return utils.makeURL(this.ctx, this.listUrlSocket[index], { t: Date.now() });
470
+ }
471
+ sendWs(payload, requireId = true) {
472
+ if (this.ws && this.getSocketState() === WebSocket.OPEN) {
473
+ if (requireId)
474
+ payload.data['req_id'] = `req_${this.id++}`;
475
+ const encodedData = new TextEncoder().encode(JSON.stringify(payload.data));
476
+ const dataLength = encodedData.length;
477
+ const data = new DataView(Buffer.alloc(4 + dataLength).buffer);
478
+ data.setUint8(0, payload.version);
479
+ data.setInt32(1, payload.cmd, true);
480
+ data.setInt8(3, payload.subCmd);
481
+ encodedData.forEach((e, i) => {
482
+ data.setUint8(4 + i, e);
483
+ });
484
+ this.ws.send(data);
485
+ }
486
+ }
487
+ shouldPingHttp() {
488
+ var _a;
489
+ const pingHttpLevel = ((_a = this.settingSocket) === null || _a === void 0 ? void 0 : _a.ping_http_level) || 0;
490
+ if (pingHttpLevel === 1) {
491
+ return Object.values(this.pingHttp).length === 0;
492
+ }
493
+ if (pingHttpLevel === 2) {
494
+ return !this.pingHttp[this.url];
495
+ }
496
+ return false;
497
+ }
498
+ ping(shouldTimeout = true) {
499
+ const currentTimestamp = Date.now();
500
+ const isPingAvailable = !this.pingTimer && this.getSocketState() === WebSocket.OPEN;
501
+ if (!isPingAvailable)
502
+ return;
503
+ this.sendWs({
504
+ version: 1,
505
+ cmd: Listen.MessageCommand.Ping,
506
+ subCmd: Listen.KeepAliveCommand.KeepConnection,
507
+ data: { eventId: currentTimestamp },
508
+ }, false);
509
+ if (shouldTimeout) {
510
+ this.pingTimer = setTimeout(() => {
511
+ if (!(!this.ws || this.getSocketState() === WebSocket.CLOSED)) {
512
+ this.handleError(Listen.ConnectionErrorCode.PingTimeout);
513
+ }
514
+ this.pingTimer = null;
515
+ }, 5000);
516
+ }
517
+ this.lastPing = currentTimestamp;
518
+ }
519
+ pingActive() {
520
+ this.sendWs({
521
+ version: 1,
522
+ cmd: Listen.MessageCommand.PingActive,
523
+ subCmd: Listen.KeepAliveCommand.KeepConnection,
524
+ data: { at: 2, reqId: 'cmd_4' },
525
+ });
526
+ }
527
+ pong(data) {
528
+ if (data.eventId === this.lastPing && this.pingTimer) {
529
+ this._resetCountRetry([Listen.ConnectionErrorCode.PingTimeout, Listen.ConnectionErrorCode.PingPong]);
530
+ clearTimeout(this.pingTimer);
531
+ this.pingTimer = null;
532
+ this.lastPong = Date.now();
533
+ }
534
+ }
535
+ handleError(errorCode) {
536
+ console.log('socket got error', errorCode, this.ctx.uid);
537
+ if (this.shouldCloseAndReconnect(errorCode)) {
538
+ this.retryAfterClose = errorCode;
539
+ this.close(Listen.CloseReason.NormalClosure);
540
+ return;
541
+ }
542
+ switch (errorCode) {
543
+ case Listen.CloseReason.NotFoundWorker:
544
+ this.retry(Listen.CloseReason.NotFoundWorker);
545
+ break;
546
+ case Listen.ConnectionErrorCode.InvalidConfig:
547
+ this.stopRetry(Listen.ConnectionErrorCode.InvalidConfig);
548
+ break;
549
+ case Listen.ConnectionErrorCode.EstablishTimeout:
550
+ this.retryAfterClose = Listen.ConnectionErrorCode.EstablishTimeout;
551
+ this.close(Listen.CloseReason.NormalClosure);
552
+ break;
553
+ case Listen.ConnectionErrorCode.PingTimeout:
554
+ const retryDelay = this._nextRetry(Listen.ConnectionErrorCode.PingPong);
555
+ if (retryDelay == null) {
556
+ this.retryAfterClose = Listen.ConnectionErrorCode.PingTimeout;
557
+ this.close(Listen.CloseReason.NormalClosure);
558
+ }
559
+ else {
560
+ setTimeout(() => {
561
+ this.ping();
562
+ }, retryDelay);
563
+ }
564
+ break;
565
+ default:
566
+ console.log('Unhandled error code', errorCode);
567
+ break;
568
+ }
569
+ }
570
+ getSocketState() {
571
+ return this.ws ? this.ws.readyState : -1;
572
+ }
573
+ reset() {
574
+ if (this.ws) {
575
+ this.ws.removeEventListener('error', (event) => { });
576
+ this.ws.removeEventListener('open', (event) => { });
577
+ this.ws.removeEventListener('message', (event) => { });
578
+ this.ws.removeEventListener('close', (event) => { });
579
+ this.ws.close();
580
+ this.ws = null;
581
+ }
582
+ this.cipherKey = undefined;
583
+ if (this.pingInterval)
584
+ clearInterval(this.pingInterval);
585
+ }
586
+ afterClose() {
587
+ this.reset();
588
+ if (this.retryAfterClose) {
589
+ this.retry(this.retryAfterClose);
590
+ this.retryAfterClose = undefined;
591
+ }
592
+ }
593
+ isEnableCloseTimeout() {
594
+ return this.settingSocket && (this.settingSocket.close_timeout || 0) > 0;
595
+ }
596
+ close(code = Listen.CloseReason.NormalClosure) {
597
+ var _a;
598
+ if (this.isEnableCloseTimeout()) {
599
+ return this.closeV2();
600
+ }
601
+ if (!this.ws) {
602
+ this.afterClose();
603
+ return;
604
+ }
605
+ const socketState = this.getSocketState();
606
+ if (socketState === WebSocket.OPEN || socketState === WebSocket.CONNECTING) {
607
+ (_a = this.ws) === null || _a === void 0 ? void 0 : _a.close(code);
608
+ }
609
+ else {
610
+ this.afterClose();
611
+ }
612
+ }
613
+ closeV2(code = Listen.CloseReason.NormalClosure) {
614
+ if (!this.ws) {
615
+ this.reset();
616
+ return this.afterClose();
617
+ }
618
+ if (this.getSocketState() === WebSocket.CLOSED) {
619
+ return this.afterClose();
620
+ }
621
+ if (!this.closeTimer) {
622
+ const socketRef = this.ws;
623
+ this.closeTimer = setTimeout(() => {
624
+ this.closeTimer = null;
625
+ this._onClose({ code: code, target: socketRef });
626
+ }, this.settingSocket.close_timeout);
627
+ }
628
+ this.ws.close(code);
629
+ }
630
+ shouldRetry() {
631
+ if (this.retryTimer) {
632
+ return false;
633
+ }
634
+ if (this.processingRetry) {
635
+ return false;
636
+ }
637
+ return true;
638
+ }
639
+ _nextRetry(errorCode) {
640
+ var _a;
641
+ if (errorCode == null) {
642
+ return Listen.DEFAULT_RETRY_DELAYS[Listen.DEFAULT_RETRY_DELAYS.length - 1];
643
+ }
644
+ const retryConfig = this.retries[errorCode];
645
+ if (retryConfig) {
646
+ let { times, count, max } = retryConfig;
647
+ if (max != null && count >= max) {
648
+ return null;
649
+ }
650
+ this.retries[errorCode].count = ((_a = this.retries[errorCode].count) !== null && _a !== void 0 ? _a : 0) + 1;
651
+ if (typeof times === 'number') {
652
+ return times;
653
+ }
654
+ if (!Array.isArray(times)) {
655
+ times = Listen.DEFAULT_RETRY_DELAYS;
656
+ }
657
+ return count < times.length ? times[count] : times[times.length - 1];
658
+ }
659
+ return Listen.DEFAULT_RETRY_DELAYS[Listen.DEFAULT_RETRY_DELAYS.length - 1];
660
+ }
661
+ shouldRotateEndPoint(errorCode) {
662
+ var _a;
663
+ const totalEndpoints = this.listUrlSocket.length;
664
+ const rotateErrorCodes = ((_a = this.settingSocket) === null || _a === void 0 ? void 0 : _a.rotate_error_codes) || [];
665
+ const shouldRotate = rotateErrorCodes.includes(errorCode) || rotateErrorCodes.includes(String(errorCode));
666
+ if (!shouldRotate) {
667
+ return false;
668
+ }
669
+ if (this.rotateCount < totalEndpoints - 1) {
670
+ this.rotateCount++;
671
+ return true;
672
+ }
673
+ return false;
674
+ }
675
+ rotateEndpoint() {
676
+ var _a, _b;
677
+ const newEndpoint = this.getSocketUrl(true);
678
+ this.url = newEndpoint;
679
+ const shouldResetEndpoint = (_a = this.settingSocket) === null || _a === void 0 ? void 0 : _a.reset_endpoint;
680
+ if (shouldResetEndpoint && !this.resetEndPointTime) {
681
+ this.resetEndPointTime = setTimeout(() => {
682
+ const originEndpoint = this.getSocketUrl(false, true);
683
+ this.url = originEndpoint;
684
+ this.handleError(Listen.NetworkErrorCode.SwapEndpointNum);
685
+ this.rotateCount = 0;
686
+ this.resetEndPointTime = null;
687
+ }, (_b = this.settingSocket) === null || _b === void 0 ? void 0 : _b.reset_endpoint);
688
+ }
689
+ }
690
+ _resetCountRetry(retryKeys) {
691
+ if (!retryKeys)
692
+ return;
693
+ retryKeys.forEach((key) => {
694
+ if (this.retries[key]) {
695
+ this.retries[key].count = 0;
696
+ }
697
+ if (this.retriesRound2[key]) {
698
+ this.retriesRound2[key].count = 0;
699
+ }
700
+ });
701
+ }
702
+ stopRetry(errorCode) {
703
+ var _a;
704
+ if ((_a = this.settingSocket) === null || _a === void 0 ? void 0 : _a.enable_fallback_lp) {
705
+ this.retries = {};
706
+ this.close();
707
+ }
708
+ }
709
+ retry(e) {
710
+ var _a;
711
+ if ((_a = this.settingSocket) === null || _a === void 0 ? void 0 : _a.disable_lp) {
712
+ this._retryV2(e);
713
+ return;
714
+ }
715
+ if (!this.shouldRetry())
716
+ return;
717
+ let retryDelay = this._nextRetry(e);
718
+ if (retryDelay == null && this.shouldRotateEndPoint(e)) {
719
+ this.rotateEndpoint();
720
+ this._resetCountRetry([e]);
721
+ retryDelay = this._nextRetry(e);
722
+ }
723
+ if (retryDelay == null) {
724
+ this.stopRetry(e);
725
+ return;
726
+ }
727
+ this.retryTimer = setTimeout(() => {
728
+ this.reset();
729
+ this.start();
730
+ this.retryTimer = null;
731
+ }, retryDelay);
732
+ }
733
+ getRetriesRound2Key(errorCode) {
734
+ let retryKey = errorCode;
735
+ if (!retryKey || !this.retriesRound2[retryKey]) {
736
+ retryKey = 'default';
737
+ }
738
+ return retryKey;
739
+ }
740
+ nextRetryRound2(errorCode) {
741
+ var _a, _b;
742
+ const t = this.getRetriesRound2Key(errorCode);
743
+ this.retriesRound2[t].count = ((_b = (_a = this.retriesRound2[t]) === null || _a === void 0 ? void 0 : _a.count) !== null && _b !== void 0 ? _b : 0) + 1;
744
+ const { multiple: a, count: n, max: s } = this.retriesRound2[t];
745
+ return this.fibonacciRetry.getNextRetry(n, a, s);
746
+ }
747
+ isRound2NotStartYet(errorCode) {
748
+ const retryRound2Key = this.getRetriesRound2Key(errorCode);
749
+ const retryInfo = this.retriesRound2[retryRound2Key];
750
+ return !retryInfo || retryInfo.count === 0;
751
+ }
752
+ _retryV2(errorCode) {
753
+ if (!this.shouldRetry())
754
+ return;
755
+ this.processingRetry = true;
756
+ let retryDelayMs = this._nextRetry(errorCode);
757
+ if (retryDelayMs == null && this.shouldRotateEndPoint(errorCode)) {
758
+ this.rotateEndpoint();
759
+ if (this.isRound2NotStartYet(errorCode)) {
760
+ this._resetCountRetry([errorCode]);
761
+ retryDelayMs = this._nextRetry(errorCode);
762
+ }
763
+ }
764
+ const scheduleRetryCallback = () => {
765
+ this.retryTimer = setTimeout(() => {
766
+ this.reset();
767
+ this.start();
768
+ this.retryTimer = null;
769
+ }, retryDelayMs || 0);
770
+ };
771
+ if (retryDelayMs == null) {
772
+ {
773
+ this.stopRetry(errorCode);
774
+ }
775
+ }
776
+ else {
777
+ scheduleRetryCallback();
778
+ }
779
+ this.processingRetry = false;
780
+ }
781
+ _configRetriesRound2() {
782
+ const retriesConfig = this.settingSocket.retries_round_2;
783
+ if (typeof retriesConfig !== 'object' || retriesConfig === null) {
784
+ return;
785
+ }
786
+ console.log('retriesConfig: ', this.ctx.uid, retriesConfig);
787
+ Object.entries(retriesConfig).forEach(([key, config]) => {
788
+ const { multiple, max } = config;
789
+ if (typeof multiple === 'number') {
790
+ this.retriesRound2[key] = {
791
+ multiple,
792
+ count: 0,
793
+ max,
794
+ };
795
+ }
796
+ });
797
+ }
798
+ createPingTimerServer() {
799
+ if (this.pingTimerToServer || !this.onlineConfigs.enable_active_deactive_v2)
800
+ return;
801
+ this.configChanged = false;
802
+ this.pingTimerToServer = setInterval(() => {
803
+ this.pingActive();
804
+ if (this.configChanged) {
805
+ this.clearPingTimer();
806
+ this.createPingTimerServer();
807
+ }
808
+ }, this.onlineConfigs.update_action_interval);
809
+ }
810
+ clearPingTimer() {
811
+ clearInterval(this.pingTimerToServer);
812
+ this.pingTimerToServer = null;
813
+ }
814
+ }
815
+ function getHeader(buffer) {
816
+ if (buffer.byteLength < 4) {
817
+ throw new Error('Invalid header');
818
+ }
819
+ return [buffer[0], buffer.readUInt16LE(1), buffer[3]];
820
+ }
821
+
822
+ exports.Listener = Listener;