@softtynet/zca-js 2.3.3 → 2.3.5

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 (184) hide show
  1. package/README.md +99 -21
  2. package/dist/apis/auth/exportSession.d.ts +7 -0
  3. package/dist/apis/auth/exportSession.js +17 -0
  4. package/dist/apis/call/addUserGroupCall.d.ts +11 -0
  5. package/dist/apis/call/addUserGroupCall.js +48 -0
  6. package/dist/apis/call/answerAckVoiceCall.d.ts +5 -0
  7. package/dist/apis/call/answerAckVoiceCall.js +38 -0
  8. package/dist/apis/call/answerGroupCall.d.ts +10 -0
  9. package/dist/apis/call/answerGroupCall.js +45 -0
  10. package/dist/apis/call/answerVoiceCall.d.ts +11 -0
  11. package/dist/apis/call/answerVoiceCall.js +50 -0
  12. package/dist/apis/call/broadcastGroupCallState.d.ts +10 -0
  13. package/dist/apis/call/broadcastGroupCallState.js +45 -0
  14. package/dist/apis/call/cancelGroupCall.d.ts +10 -0
  15. package/dist/apis/call/cancelGroupCall.js +45 -0
  16. package/dist/apis/call/cancelVoiceCall.d.ts +1 -1
  17. package/dist/apis/call/cancelVoiceCall.js +5 -1
  18. package/dist/apis/call/createController.d.ts +7 -0
  19. package/dist/apis/call/createController.js +9 -0
  20. package/dist/apis/call/createRtcSession.d.ts +8 -0
  21. package/dist/apis/call/createRtcSession.js +10 -0
  22. package/dist/apis/call/endGroupCall.d.ts +10 -0
  23. package/dist/apis/call/endGroupCall.js +45 -0
  24. package/dist/apis/call/finishGroupCall.d.ts +12 -0
  25. package/dist/apis/call/finishGroupCall.js +47 -0
  26. package/dist/apis/call/getGroupCallState.d.ts +3 -0
  27. package/dist/apis/call/getGroupCallState.js +38 -0
  28. package/dist/apis/call/holdVoiceCall.d.ts +5 -0
  29. package/dist/apis/call/holdVoiceCall.js +34 -0
  30. package/dist/apis/call/joinGroupCall.d.ts +11 -0
  31. package/dist/apis/call/joinGroupCall.js +46 -0
  32. package/dist/apis/call/logEndVoiceCall.d.ts +8 -0
  33. package/dist/apis/call/logEndVoiceCall.js +46 -0
  34. package/dist/apis/call/notifyGroupCall.d.ts +9 -0
  35. package/dist/apis/call/notifyGroupCall.js +44 -0
  36. package/dist/apis/call/pingGroupCall.d.ts +10 -0
  37. package/dist/apis/call/pingGroupCall.js +45 -0
  38. package/dist/apis/call/requestAudioToVideoCall.d.ts +5 -0
  39. package/dist/apis/call/requestAudioToVideoCall.js +37 -0
  40. package/dist/apis/call/requestGroupCall.d.ts +9 -0
  41. package/dist/apis/call/requestGroupCall.js +46 -0
  42. package/dist/apis/call/requestShareScreenCall.d.ts +5 -0
  43. package/dist/apis/call/requestShareScreenCall.js +37 -0
  44. package/dist/apis/call/requestVoiceCall.d.ts +10 -0
  45. package/dist/apis/call/requestVoiceCall.js +47 -0
  46. package/dist/apis/call/ringRingGroupCall.d.ts +10 -0
  47. package/dist/apis/call/ringRingGroupCall.js +45 -0
  48. package/dist/apis/call/ringRingVoiceCall.d.ts +5 -0
  49. package/dist/apis/call/ringRingVoiceCall.js +38 -0
  50. package/dist/apis/call/sendRequestGroupCall.d.ts +11 -0
  51. package/dist/apis/call/sendRequestGroupCall.js +48 -0
  52. package/dist/apis/call/sendRequestVoiceCall.d.ts +11 -0
  53. package/dist/apis/call/sendRequestVoiceCall.js +51 -0
  54. package/dist/apis/call/sendVoiceCall.d.ts +8 -1
  55. package/dist/apis/call/sendVoiceCall.js +10 -8
  56. package/dist/apis/call/shareScreenGroupCall.d.ts +10 -0
  57. package/dist/apis/call/shareScreenGroupCall.js +45 -0
  58. package/dist/apis/call/unholdVoiceCall.d.ts +5 -0
  59. package/dist/apis/call/unholdVoiceCall.js +34 -0
  60. package/dist/apis/call/updateCallMediaState.d.ts +7 -0
  61. package/dist/apis/call/updateCallMediaState.js +61 -0
  62. package/dist/apis/chat/getDirectMessageStatus.d.ts +14 -0
  63. package/dist/apis/chat/getDirectMessageStatus.js +52 -0
  64. package/dist/apis/chat/getSuggestedConversations.d.ts +5 -0
  65. package/dist/apis/chat/getSuggestedConversations.js +24 -0
  66. package/dist/apis/chat/updatePinConversations.d.ts +24 -0
  67. package/dist/apis/chat/updatePinConversations.js +40 -0
  68. package/dist/apis/friend/getFriendProfilesV2.d.ts +26 -0
  69. package/dist/apis/friend/getFriendProfilesV2.js +38 -0
  70. package/dist/apis/friend/getFriendsActivePCWeb.d.ts +6 -0
  71. package/dist/apis/friend/getFriendsActivePCWeb.js +15 -0
  72. package/dist/apis/friend/verifyFriend.d.ts +11 -0
  73. package/dist/apis/friend/verifyFriend.js +37 -0
  74. package/dist/apis/group/analyseGroup.d.ts +4 -0
  75. package/dist/apis/group/analyseGroup.js +28 -0
  76. package/dist/apis/group/checkQuotaCommunityMember.d.ts +6 -0
  77. package/dist/apis/group/checkQuotaCommunityMember.js +34 -0
  78. package/dist/apis/group/getE2eeGroupList.d.ts +7 -0
  79. package/dist/apis/group/getE2eeGroupList.js +15 -0
  80. package/dist/apis/group/getGroupCloudMessagePath.d.ts +19 -0
  81. package/dist/apis/group/getGroupCloudMessagePath.js +51 -0
  82. package/dist/apis/group/getMultiGroupCloudMessages.d.ts +5 -0
  83. package/dist/apis/group/getMultiGroupCloudMessages.js +29 -0
  84. package/dist/apis/media/getPersonalMedia.d.ts +8 -0
  85. package/dist/apis/media/getPersonalMedia.js +31 -0
  86. package/dist/apis/media/getPersonalMediaPreview.d.ts +3 -0
  87. package/dist/apis/media/getPersonalMediaPreview.js +24 -0
  88. package/dist/apis/media/removePersonalMedia.d.ts +9 -0
  89. package/dist/apis/media/removePersonalMedia.js +28 -0
  90. package/dist/apis.d.ts +126 -0
  91. package/dist/apis.js +210 -0
  92. package/dist/call-engine/ZRTPPacketCodec.d.ts +262 -0
  93. package/dist/call-engine/ZRTPPacketCodec.js +506 -0
  94. package/dist/call-engine/ZaloCallController.d.ts +134 -0
  95. package/dist/call-engine/ZaloCallController.js +638 -0
  96. package/dist/call-engine/ZaloCallTransport.d.ts +83 -0
  97. package/dist/call-engine/ZaloCallTransport.js +233 -0
  98. package/dist/call-engine/ZaloRtcSession.d.ts +609 -0
  99. package/dist/call-engine/ZaloRtcSession.js +2931 -0
  100. package/dist/call-engine/ZaloSrtpSession.d.ts +80 -0
  101. package/dist/call-engine/ZaloSrtpSession.js +278 -0
  102. package/dist/call-engine/index.d.ts +18 -0
  103. package/dist/call-engine/index.js +24 -0
  104. package/dist/cjs/apis/auth/exportSession.cjs +22 -0
  105. package/dist/cjs/apis/call/addUserGroupCall.cjs +53 -0
  106. package/dist/cjs/apis/call/answerAckVoiceCall.cjs +43 -0
  107. package/dist/cjs/apis/call/answerGroupCall.cjs +50 -0
  108. package/dist/cjs/apis/call/answerVoiceCall.cjs +55 -0
  109. package/dist/cjs/apis/call/broadcastGroupCallState.cjs +50 -0
  110. package/dist/cjs/apis/call/cancelGroupCall.cjs +50 -0
  111. package/dist/cjs/apis/call/cancelVoiceCall.cjs +5 -1
  112. package/dist/cjs/apis/call/createController.cjs +14 -0
  113. package/dist/cjs/apis/call/createRtcSession.cjs +15 -0
  114. package/dist/cjs/apis/call/endGroupCall.cjs +50 -0
  115. package/dist/cjs/apis/call/finishGroupCall.cjs +52 -0
  116. package/dist/cjs/apis/call/getGroupCallState.cjs +43 -0
  117. package/dist/cjs/apis/call/holdVoiceCall.cjs +39 -0
  118. package/dist/cjs/apis/call/joinGroupCall.cjs +51 -0
  119. package/dist/cjs/apis/call/logEndVoiceCall.cjs +51 -0
  120. package/dist/cjs/apis/call/notifyGroupCall.cjs +49 -0
  121. package/dist/cjs/apis/call/pingGroupCall.cjs +50 -0
  122. package/dist/cjs/apis/call/requestAudioToVideoCall.cjs +42 -0
  123. package/dist/cjs/apis/call/requestGroupCall.cjs +51 -0
  124. package/dist/cjs/apis/call/requestShareScreenCall.cjs +42 -0
  125. package/dist/cjs/apis/call/requestVoiceCall.cjs +52 -0
  126. package/dist/cjs/apis/call/ringRingGroupCall.cjs +50 -0
  127. package/dist/cjs/apis/call/ringRingVoiceCall.cjs +43 -0
  128. package/dist/cjs/apis/call/sendRequestGroupCall.cjs +53 -0
  129. package/dist/cjs/apis/call/sendRequestVoiceCall.cjs +56 -0
  130. package/dist/cjs/apis/call/sendVoiceCall.cjs +10 -8
  131. package/dist/cjs/apis/call/shareScreenGroupCall.cjs +50 -0
  132. package/dist/cjs/apis/call/unholdVoiceCall.cjs +39 -0
  133. package/dist/cjs/apis/call/updateCallMediaState.cjs +66 -0
  134. package/dist/cjs/apis/chat/getDirectMessageStatus.cjs +58 -0
  135. package/dist/cjs/apis/chat/getSuggestedConversations.cjs +29 -0
  136. package/dist/cjs/apis/chat/updatePinConversations.cjs +45 -0
  137. package/dist/cjs/apis/friend/getFriendProfilesV2.cjs +43 -0
  138. package/dist/cjs/apis/friend/getFriendsActivePCWeb.cjs +20 -0
  139. package/dist/cjs/apis/friend/verifyFriend.cjs +42 -0
  140. package/dist/cjs/apis/group/analyseGroup.cjs +33 -0
  141. package/dist/cjs/apis/group/checkQuotaCommunityMember.cjs +39 -0
  142. package/dist/cjs/apis/group/getE2eeGroupList.cjs +20 -0
  143. package/dist/cjs/apis/group/getGroupChatHistory.cjs +8 -7
  144. package/dist/cjs/apis/group/getGroupCloudMessagePath.cjs +69 -0
  145. package/dist/cjs/apis/group/getGroupHistoryLegacy.cjs +7 -6
  146. package/dist/cjs/apis/group/getGroupMessageStatus.cjs +7 -6
  147. package/dist/cjs/apis/group/getMultiGroupCloudMessages.cjs +34 -0
  148. package/dist/cjs/apis/media/getPersonalMedia.cjs +36 -0
  149. package/dist/cjs/apis/media/getPersonalMediaPreview.cjs +29 -0
  150. package/dist/cjs/apis/media/removePersonalMedia.cjs +33 -0
  151. package/dist/cjs/apis.cjs +210 -0
  152. package/dist/cjs/call-engine/ZRTPPacketCodec.cjs +525 -0
  153. package/dist/cjs/call-engine/ZaloCallController.cjs +643 -0
  154. package/dist/cjs/call-engine/ZaloCallTransport.cjs +239 -0
  155. package/dist/cjs/call-engine/ZaloRtcSession.cjs +2937 -0
  156. package/dist/cjs/call-engine/ZaloSrtpSession.cjs +287 -0
  157. package/dist/cjs/constants/protocol.cjs +1 -1
  158. package/dist/cjs/context.cjs +20 -2
  159. package/dist/cjs/core/zcloud-client.cjs +1 -2
  160. package/dist/cjs/crypto/ws-cipher.cjs +66 -2
  161. package/dist/cjs/index.cjs +63 -6
  162. package/dist/cjs/models/CallEvent.cjs +95 -1
  163. package/dist/cjs/realtime/handlers/control.handler.cjs +17 -3
  164. package/dist/cjs/realtime/listener.cjs +37 -6
  165. package/dist/cjs/update.cjs +10 -1
  166. package/dist/cjs/utils.cjs +21 -68
  167. package/dist/constants/protocol.d.ts +1 -1
  168. package/dist/constants/protocol.js +1 -1
  169. package/dist/context.d.ts +10 -0
  170. package/dist/context.js +20 -2
  171. package/dist/crypto/ws-cipher.d.ts +7 -0
  172. package/dist/crypto/ws-cipher.js +64 -2
  173. package/dist/index.d.ts +47 -4
  174. package/dist/index.js +2 -0
  175. package/dist/models/CallEvent.d.ts +15 -0
  176. package/dist/models/CallEvent.js +95 -1
  177. package/dist/realtime/handlers/control.handler.js +17 -3
  178. package/dist/realtime/listener.d.ts +6 -0
  179. package/dist/realtime/listener.js +37 -6
  180. package/dist/update.js +10 -1
  181. package/dist/utils.d.ts +0 -7
  182. package/dist/utils.js +15 -64
  183. package/index.d.ts +1 -1
  184. package/package.json +16 -5
@@ -4,11 +4,24 @@ exports.CallEventType = void 0;
4
4
  (function (CallEventType) {
5
5
  CallEventType["REQUEST"] = "request";
6
6
  CallEventType["ANSWER"] = "answer";
7
+ CallEventType["ANSWER_ACK"] = "answer_ack";
7
8
  CallEventType["CANCEL"] = "cancel";
8
9
  CallEventType["END"] = "end";
9
10
  CallEventType["RINGRING"] = "ringring";
11
+ CallEventType["ONOFF_CAMERA"] = "onoff_camera";
12
+ CallEventType["MUTE_AUDIO"] = "mute_audio";
13
+ CallEventType["AUDIO_TO_VIDEO"] = "audio_to_video";
14
+ CallEventType["SHARE_SCREEN"] = "share_screen";
10
15
  CallEventType["GROUP_REQUEST"] = "group_request";
16
+ CallEventType["GROUP_RINGRING"] = "group_ring_ring";
11
17
  CallEventType["GROUP_ANSWER"] = "group_answer";
18
+ CallEventType["GROUP_ANSWER_ACK"] = "group_answer_ack";
19
+ CallEventType["GROUP_CANCEL"] = "group_cancel";
20
+ CallEventType["GROUP_END"] = "group_end_call";
21
+ CallEventType["GROUP_ADD_USER"] = "group_add_user";
22
+ CallEventType["GROUP_NOTIFY"] = "group_notify";
23
+ CallEventType["GROUP_BROADCAST"] = "group_broadcast";
24
+ CallEventType["GROUP_BROADCAST_CONFIG"] = "group_broadcast_config";
12
25
  CallEventType["UNKNOWN"] = "unknown";
13
26
  })(exports.CallEventType || (exports.CallEventType = {}));
14
27
  function initializeCallEvent(data, act, act_type) {
@@ -20,31 +33,112 @@ function initializeCallEvent(data, act, act_type) {
20
33
  case "answer":
21
34
  type = exports.CallEventType.ANSWER;
22
35
  break;
36
+ case "answer_ack":
37
+ case "answerack":
38
+ type = exports.CallEventType.ANSWER_ACK;
39
+ break;
23
40
  case "cancel":
24
41
  type = exports.CallEventType.CANCEL;
25
42
  break;
26
43
  case "end":
27
44
  case "endcall":
45
+ case "end_call":
28
46
  type = exports.CallEventType.END;
29
47
  break;
30
48
  case "ringring":
49
+ case "ring_ring":
31
50
  type = exports.CallEventType.RINGRING;
32
51
  break;
52
+ case "onoff_camera":
53
+ type = exports.CallEventType.ONOFF_CAMERA;
54
+ break;
55
+ case "mute_audio":
56
+ type = exports.CallEventType.MUTE_AUDIO;
57
+ break;
58
+ case "audio_to_video":
59
+ type = exports.CallEventType.AUDIO_TO_VIDEO;
60
+ break;
61
+ case "share_screen":
62
+ type = exports.CallEventType.SHARE_SCREEN;
63
+ break;
33
64
  case "group_request":
34
65
  type = exports.CallEventType.GROUP_REQUEST;
35
66
  break;
67
+ case "group_ring_ring":
68
+ case "group_ringring":
69
+ type = exports.CallEventType.GROUP_RINGRING;
70
+ break;
36
71
  case "group_answer":
37
72
  type = exports.CallEventType.GROUP_ANSWER;
38
73
  break;
74
+ case "group_answer_ack":
75
+ type = exports.CallEventType.GROUP_ANSWER_ACK;
76
+ break;
77
+ case "group_cancel":
78
+ type = exports.CallEventType.GROUP_CANCEL;
79
+ break;
80
+ case "group_end_call":
81
+ case "group_endcall":
82
+ case "group_end":
83
+ type = exports.CallEventType.GROUP_END;
84
+ break;
85
+ case "group_add_user":
86
+ type = exports.CallEventType.GROUP_ADD_USER;
87
+ break;
88
+ case "group_notify":
89
+ type = exports.CallEventType.GROUP_NOTIFY;
90
+ break;
91
+ case "group_broadcast":
92
+ type = exports.CallEventType.GROUP_BROADCAST;
93
+ break;
94
+ case "group_broadcast_config":
95
+ type = exports.CallEventType.GROUP_BROADCAST_CONFIG;
96
+ break;
39
97
  default:
40
98
  type = exports.CallEventType.UNKNOWN;
41
99
  break;
42
100
  }
101
+ const enrichedData = { ...data };
102
+ let parsedParams;
103
+ if (typeof enrichedData.params === "string") {
104
+ try {
105
+ const p = JSON.parse(enrichedData.params);
106
+ if (p && typeof p === "object")
107
+ parsedParams = p;
108
+ }
109
+ catch {
110
+ // Ignore invalid JSON
111
+ }
112
+ }
113
+ else if (enrichedData.params && typeof enrichedData.params === "object") {
114
+ parsedParams = enrichedData.params;
115
+ }
116
+ if (enrichedData.callId === undefined) {
117
+ const extractedCallId = Number(enrichedData.call_id ??
118
+ enrichedData.id ??
119
+ parsedParams?.callId ??
120
+ parsedParams?.call_id ??
121
+ parsedParams?.id ??
122
+ 0);
123
+ if (extractedCallId > 0) {
124
+ enrichedData.callId = extractedCallId;
125
+ }
126
+ }
127
+ if (enrichedData.callerId === undefined) {
128
+ const extractedCallerId = enrichedData.uidN ??
129
+ parsedParams?.uidN ??
130
+ enrichedData.uidFrom ??
131
+ enrichedData.fromId ??
132
+ parsedParams?.uidFrom;
133
+ if (extractedCallerId !== undefined) {
134
+ enrichedData.callerId = String(extractedCallerId);
135
+ }
136
+ }
43
137
  return {
44
138
  type,
45
139
  act,
46
140
  act_type,
47
- data,
141
+ data: enrichedData,
48
142
  };
49
143
  }
50
144
 
@@ -8,10 +8,15 @@ var utils = require('../../utils.cjs');
8
8
  var wsCipher = require('../../crypto/ws-cipher.cjs');
9
9
 
10
10
  async function handleRealtimeControlPacket(listener, ctx, selfListen, version, cmd, subCmd, parsed, cipherKey) {
11
- if (version == 1 && cmd == 601 && subCmd == 0) {
12
- const parsedData = (await wsCipher.decodeEventData(parsed, cipherKey)).data;
13
- const { controls } = parsedData;
11
+ if (version == 1 && (cmd == 601 || cmd == 603 || cmd == 604) && subCmd == 0) {
12
+ const decoded = await wsCipher.decodeEventData(parsed, cipherKey);
13
+ const parsedData = decoded?.data;
14
+ const controls = parsedData?.controls;
15
+ if (!Array.isArray(controls))
16
+ return false;
14
17
  for (const control of controls) {
18
+ if (!control?.content)
19
+ continue;
15
20
  if (control.content.act_type == "file_done") {
16
21
  const data = {
17
22
  fileUrl: control.content.data.url,
@@ -62,6 +67,15 @@ async function handleRealtimeControlPacket(listener, ctx, selfListen, version, c
62
67
  const callEventData = typeof control.content.data == "string"
63
68
  ? JSON.parse(control.content.data)
64
69
  : control.content.data;
70
+ // Bỏ qua các gói mời gọi cũ (> 60s) nằm trong hàng đợi offline (cmd 603 / 604)
71
+ if ((cmd == 603 || cmd == 604) &&
72
+ (control.content.act == "request" || control.content.act == "group_request") &&
73
+ callEventData?.ts) {
74
+ const eventTs = Number.parseInt(String(callEventData.ts), 10);
75
+ if (Number.isFinite(eventTs) && Date.now() - eventTs > 60_000) {
76
+ continue;
77
+ }
78
+ }
65
79
  const callEvent = CallEvent.initializeCallEvent(callEventData, control.content.act, control.content.act_type);
66
80
  listener.emit("call_event", callEvent);
67
81
  }
@@ -43,6 +43,7 @@ class Listener extends EventEmitter {
43
43
  retryTimeout;
44
44
  watchdogTimeout;
45
45
  consecutive1006Count = 0;
46
+ isManualStop = false;
46
47
  id = 0;
47
48
  constructor(ctx, urls) {
48
49
  super();
@@ -168,6 +169,7 @@ class Listener extends EventEmitter {
168
169
  utils.logger(this.ctx).verbose(`Rotating endpoint to ${this.wsURL}`);
169
170
  }
170
171
  start({ retryOnClose = false } = {}) {
172
+ this.isManualStop = false;
171
173
  if (this.retryTimeout) {
172
174
  clearTimeout(this.retryTimeout);
173
175
  this.retryTimeout = undefined;
@@ -191,6 +193,10 @@ class Listener extends EventEmitter {
191
193
  t: Date.now(),
192
194
  });
193
195
  const cookie = this.ctx.cookie.getCookieStringSync(domains.ZALO_DOMAINS.CHAT_WEB);
196
+ const wsAgent = this.ctx.options.agent &&
197
+ typeof this.ctx.options.agent.dispatch !== "function"
198
+ ? this.ctx.options.agent
199
+ : undefined;
194
200
  const ws = new WebSocket(this.wsURL, {
195
201
  headers: {
196
202
  "accept-language": utils.getBrowserLanguage(this.ctx.language),
@@ -205,7 +211,7 @@ class Listener extends EventEmitter {
205
211
  "user-agent": this.userAgent,
206
212
  cookie,
207
213
  },
208
- agent: this.ctx.options.agent,
214
+ ...(wsAgent ? { agent: wsAgent } : {}),
209
215
  });
210
216
  this.ws = ws;
211
217
  ws.onopen = () => {
@@ -218,7 +224,8 @@ class Listener extends EventEmitter {
218
224
  ws.onclose = (event) => {
219
225
  this.reset();
220
226
  this.emit("disconnected", event.code, event.reason);
221
- const retry = retryOnClose &&
227
+ const retry = !this.isManualStop &&
228
+ retryOnClose &&
222
229
  this.isRetryable(event.code) &&
223
230
  this.canRetry(event.code);
224
231
  if (retry && retryOnClose) {
@@ -262,6 +269,7 @@ class Listener extends EventEmitter {
262
269
  if (version == 1 && cmd == 1 && subCmd == 1 && utils.hasOwn(parsed, "key")) {
263
270
  this.cipherKey = parsed.key;
264
271
  this.emit("cipher_key", this.cipherKey);
272
+ this.requestOfflineVoipControls();
265
273
  if (this.pingInterval)
266
274
  clearInterval(this.pingInterval);
267
275
  const ping = () => {
@@ -304,19 +312,24 @@ class Listener extends EventEmitter {
304
312
  };
305
313
  }
306
314
  stop() {
307
- if (this.retryTimeout)
315
+ this.isManualStop = true;
316
+ if (this.retryTimeout) {
308
317
  clearTimeout(this.retryTimeout);
309
- if (this.watchdogTimeout)
318
+ this.retryTimeout = undefined;
319
+ }
320
+ if (this.watchdogTimeout) {
310
321
  clearTimeout(this.watchdogTimeout);
322
+ this.watchdogTimeout = undefined;
323
+ }
311
324
  if (this.ws) {
312
- if (this.ws.readyState !== WebSocket.CLOSED) {
325
+ if (this.ws.readyState !== WebSocket.CLOSED && this.ws.readyState !== WebSocket.CLOSING) {
313
326
  this.ws.close(types.CloseReason.ManualClosure);
314
327
  }
315
328
  }
316
329
  this.reset();
317
330
  }
318
331
  sendWs(payload, requireId = true) {
319
- if (this.ws) {
332
+ if (this.ws && (this.ws.readyState === undefined || this.ws.readyState === WebSocket.OPEN)) {
320
333
  if (requireId)
321
334
  payload.data["req_id"] = `req_${this.id++}`;
322
335
  const encodedData = new TextEncoder().encode(JSON.stringify(payload.data));
@@ -362,6 +375,24 @@ class Listener extends EventEmitter {
362
375
  };
363
376
  this.sendWs(payload);
364
377
  }
378
+ /**
379
+ * Kích hoạt và kéo hàng đợi sự kiện điều khiển / VoIP (`cmd 603` CTRL_PULL_CTRL và `cmd 604` CTRL_PULL_VOIP).
380
+ * Giúp nhận ngay các cuộc gọi đến đang đổ chuông ngay cả khi vừa mở kết nối WebSocket.
381
+ */
382
+ requestOfflineVoipControls() {
383
+ this.sendWs({
384
+ version: 1,
385
+ cmd: 604,
386
+ subCmd: 0,
387
+ data: { first: true, lastId: 1, preIds: [] },
388
+ });
389
+ this.sendWs({
390
+ version: 1,
391
+ cmd: 603,
392
+ subCmd: 0,
393
+ data: { first: true, lastId: "1", preIds: [] },
394
+ });
395
+ }
365
396
  reset() {
366
397
  this.ws = null;
367
398
  this.cipherKey = undefined;
@@ -10,6 +10,9 @@ const NPM_REGISTRY = domains.ZALO_DOMAINS.NPM_REGISTRY;
10
10
  async function checkUpdate(ctx) {
11
11
  if (!ctx.options.checkUpdate)
12
12
  return;
13
+ const isAgentUndiciDispatcher = Boolean(ctx.options.agent && typeof ctx.options.agent.dispatch === "function");
14
+ const resolvedDispatcher = ctx.options.dispatcher ?? (isAgentUndiciDispatcher ? ctx.options.agent : undefined);
15
+ const resolvedAgent = ctx.options.agent && !isAgentUndiciDispatcher ? ctx.options.agent : undefined;
13
16
  const _options = {
14
17
  ...(utils.isBun
15
18
  ? {
@@ -17,7 +20,13 @@ async function checkUpdate(ctx) {
17
20
  // @ts-ignore
18
21
  proxy: ctx.options.agent?.proxy?.href,
19
22
  }
20
- : { agent: ctx.options.agent }),
23
+ : {
24
+ ...(resolvedAgent ? { agent: resolvedAgent } : {}),
25
+ ...(resolvedDispatcher ? { dispatcher: resolvedDispatcher } : {}),
26
+ }),
27
+ ...(typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function"
28
+ ? { signal: AbortSignal.timeout(3000) }
29
+ : {}),
21
30
  };
22
31
  const response = await ctx.options.polyfill(NPM_REGISTRY, _options).catch(() => null);
23
32
  if (!response || !response.ok)
@@ -15,8 +15,7 @@ var protocol = require('./constants/protocol.cjs');
15
15
  var domains = require('./constants/domains.cjs');
16
16
  var defaults = require('./constants/defaults.cjs');
17
17
  var aes = require('./crypto/aes.cjs');
18
- require('pako');
19
- require('json-bigint');
18
+ var wsCipher = require('./crypto/ws-cipher.cjs');
20
19
 
21
20
  const isBun = typeof Bun !== "undefined";
22
21
  function hasOwn(obj, key) {
@@ -166,8 +165,20 @@ async function request(ctx, url, options, raw = false) {
166
165
  options = { ...(options || {}), headers: defaultHeaders };
167
166
  }
168
167
  }
168
+ const timeoutMs = ctx.options?.requestTimeout;
169
+ const defaultSignal = !options?.signal &&
170
+ typeof timeoutMs === "number" &&
171
+ timeoutMs > 0 &&
172
+ typeof AbortSignal !== "undefined" &&
173
+ typeof AbortSignal.timeout === "function"
174
+ ? AbortSignal.timeout(timeoutMs)
175
+ : undefined;
176
+ const isAgentUndiciDispatcher = Boolean(ctx.options.agent && typeof ctx.options.agent.dispatch === "function");
177
+ const resolvedDispatcher = ctx.options.dispatcher ?? (isAgentUndiciDispatcher ? ctx.options.agent : undefined);
178
+ const resolvedAgent = ctx.options.agent && !isAgentUndiciDispatcher ? ctx.options.agent : undefined;
169
179
  const _options = {
170
180
  ...(options ?? {}),
181
+ ...(defaultSignal ? { signal: defaultSignal } : {}),
171
182
  ...(isBun
172
183
  ? {
173
184
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -175,8 +186,8 @@ async function request(ctx, url, options, raw = false) {
175
186
  proxy: ctx.options.agent?.proxy?.href,
176
187
  }
177
188
  : {
178
- agent: ctx.options.agent,
179
- dispatcher: ctx.options.agent,
189
+ ...(resolvedAgent ? { agent: resolvedAgent } : {}),
190
+ ...(resolvedDispatcher ? { dispatcher: resolvedDispatcher } : {}),
180
191
  }),
181
192
  };
182
193
  const response = await ctx.options.polyfill(url, _options);
@@ -452,67 +463,6 @@ function getFriendEventType(act) {
452
463
  return FriendEvent.FriendEventType.PIN_CREATE;
453
464
  return FriendEvent.FriendEventType.UNKNOWN;
454
465
  }
455
- const DEFAULT_BIGINT_ID_FIELDS = [
456
- "owner_id",
457
- "group_id",
458
- "uidFrom",
459
- "uidTo",
460
- "uid",
461
- "ownerId",
462
- "destId",
463
- "srcId",
464
- "fromUid",
465
- "threadId",
466
- "globalMsgId",
467
- "global_msg_id",
468
- "globalDelMsgId",
469
- "msgId",
470
- "refMessageId",
471
- "cliMsgId",
472
- "client_msg_id",
473
- "actionId",
474
- "lastActionIdOther",
475
- "lastActionId",
476
- "resetLastActionId",
477
- "lastId",
478
- "ids",
479
- "topicId",
480
- "reminderId",
481
- "eventId",
482
- ];
483
- /**
484
- * Tiền xử lý chuỗi JSON trước khi `JSON.parse` (tương đương `preParse(e, t)` trong `app.asar` của Zalo PC).
485
- * Bọc dấu ngoặc kép quanh các trường định danh 64-bit (`owner_id`, `group_id`, hoặc các số nguyên >= 16 chữ số
486
- * vượt quá `Number.MAX_SAFE_INTEGER`) để tránh mất 3 chữ số cuối của UID/MsgId do làm tròn IEEE-754.
487
- */
488
- function preParseJsonIds(jsonStr, alwaysStringFields = ["owner_id", "group_id"], bigIntSafeFields = DEFAULT_BIGINT_ID_FIELDS) {
489
- if (!jsonStr || typeof jsonStr !== "string")
490
- return jsonStr;
491
- const alwaysSet = new Set(alwaysStringFields);
492
- const allFields = Array.from(new Set([...alwaysStringFields, ...bigIntSafeFields]));
493
- if (allFields.length === 0)
494
- return jsonStr;
495
- const escapedKeys = allFields.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
496
- const keyValueRegex = new RegExp(`"(${escapedKeys})"(\\s*:\\s*)(\\d+|\\[\\s*\\d+(?:\\s*,\\s*\\d+)*\\s*\\])`, "g");
497
- return jsonStr.replace(keyValueRegex, (fullMatch, key, sep, rawVal) => {
498
- const forceString = alwaysSet.has(key);
499
- if (rawVal.startsWith("[")) {
500
- const inner = rawVal.slice(1, -1);
501
- const parts = inner.split(",").map((part) => {
502
- const trimmed = part.trim();
503
- if (forceString || trimmed.length >= 16 || Number(trimmed) > Number.MAX_SAFE_INTEGER) {
504
- return `"${trimmed}"`;
505
- }
506
- return trimmed;
507
- });
508
- return `"${key}"${sep}[${parts.join(",")}]`;
509
- }
510
- if (forceString || rawVal.length >= 16 || Number(rawVal) > Number.MAX_SAFE_INTEGER) {
511
- return `"${key}"${sep}"${rawVal}"`;
512
- }
513
- return fullMatch;
514
- });
515
- }
516
466
  async function handleZaloResponse(ctx, response, isEncrypted = true) {
517
467
  const result = {
518
468
  data: null,
@@ -542,7 +492,7 @@ async function handleZaloResponse(ctx, response, isEncrypted = true) {
542
492
  result.data = {};
543
493
  return result;
544
494
  }
545
- const safeJsonStr = isEncrypted ? preParseJsonIds(decodedStr) : null;
495
+ const safeJsonStr = isEncrypted ? wsCipher.preParseJsonIds(decodedStr) : null;
546
496
  const decodedData = isEncrypted ? JSON.parse(safeJsonStr) : jsonData;
547
497
  if (decodedData.error_code != 0) {
548
498
  result.error = {
@@ -616,7 +566,11 @@ exports.decryptResp = aes.decryptResp;
616
566
  exports.encodeAES = aes.encodeAES;
617
567
  exports.encryptPin = aes.encryptPin;
618
568
  exports.validatePin = aes.validatePin;
619
- exports.DEFAULT_BIGINT_ID_FIELDS = DEFAULT_BIGINT_ID_FIELDS;
569
+ exports.DEFAULT_BIGINT_ID_FIELDS = wsCipher.DEFAULT_BIGINT_ID_FIELDS;
570
+ exports.decodeBase64ToBuffer = wsCipher.decodeBase64ToBuffer;
571
+ exports.decodeEventData = wsCipher.decodeEventData;
572
+ exports.decodeUnit8Array = wsCipher.decodeUnit8Array;
573
+ exports.preParseJsonIds = wsCipher.preParseJsonIds;
620
574
  exports.apiFactory = apiFactory;
621
575
  exports.formatTime = formatTime;
622
576
  exports.generateZaloUUID = generateZaloUUID;
@@ -642,7 +596,6 @@ exports.isBun = isBun;
642
596
  exports.logger = logger;
643
597
  exports.makeURL = makeURL;
644
598
  exports.normalizeHolderName = normalizeHolderName;
645
- exports.preParseJsonIds = preParseJsonIds;
646
599
  exports.removeUndefinedKeys = removeUndefinedKeys;
647
600
  exports.request = request;
648
601
  exports.resolveResponse = resolveResponse;
@@ -10,7 +10,7 @@ export declare const ZALO_PROTOCOL: {
10
10
  readonly API_VERSION: 689;
11
11
  readonly DEFAULT_API_VERSION: 689;
12
12
  /** Phiên bản SDK hiện tại */
13
- readonly SDK_VERSION: "2.3.3";
13
+ readonly SDK_VERSION: "2.3.5";
14
14
  /** Mã zpw_type đặc thù dành riêng cho phân hệ Gọi Thoại & Video (binary C++ ZaloCall) */
15
15
  readonly VOICECALL_API_TYPE: 24;
16
16
  /** Phiên bản mã hóa ZCID (ParamDecryptor) */
@@ -10,7 +10,7 @@ export const ZALO_PROTOCOL = {
10
10
  API_VERSION: 689,
11
11
  DEFAULT_API_VERSION: 689,
12
12
  /** Phiên bản SDK hiện tại */
13
- SDK_VERSION: "2.3.3",
13
+ SDK_VERSION: "2.3.5",
14
14
  /** Mã zpw_type đặc thù dành riêng cho phân hệ Gọi Thoại & Video (binary C++ ZaloCall) */
15
15
  VOICECALL_API_TYPE: 24,
16
16
  /** Phiên bản mã hóa ZCID (ParamDecryptor) */
package/dist/context.d.ts CHANGED
@@ -154,18 +154,28 @@ export type Options = {
154
154
  apiType: number;
155
155
  apiVersion: number;
156
156
  agent?: Agent;
157
+ /**
158
+ * Bộ điều phối kết nối (Undici `Dispatcher` / `ProxyAgent`) dành cho `globalThis.fetch` mặc định của Node.js 18+.
159
+ */
160
+ dispatcher?: unknown;
157
161
  /**
158
162
  * Optional fetch implementation for polyfills in non-standard environments.
159
163
  * If using proxy, `node-fetch` is highly recommended.
160
164
  */
161
165
  polyfill: typeof fetch;
162
166
  imageMetadataGetter?: ImageMetadataGetter;
167
+ /**
168
+ * Thời gian chờ tối đa cho mỗi HTTP request tính bằng mili-giây (mặc định: `30000`ms = 30s, đặt `0` để tắt).
169
+ */
170
+ requestTimeout?: number;
163
171
  };
164
172
  declare class CallbacksMap extends Map<string, UploadCallback> {
173
+ private timers;
165
174
  /**
166
175
  * @param ttl Time to live in milliseconds. Default is 5 minutes.
167
176
  */
168
177
  set(key: string, value: UploadCallback, ttl?: number): this;
178
+ delete(key: string): boolean;
169
179
  }
170
180
  export type ViewerKeyCacheEntry = {
171
181
  viewer_key: string;
package/dist/context.js CHANGED
@@ -1,15 +1,32 @@
1
1
  import { ZALO_DEFAULTS, ZALO_PROTOCOL } from "./constants/index.js";
2
2
  const _5_MINUTES = ZALO_DEFAULTS.UPLOAD.CALLBACK_TTL_MS;
3
3
  class CallbacksMap extends Map {
4
+ timers = new Map();
4
5
  /**
5
6
  * @param ttl Time to live in milliseconds. Default is 5 minutes.
6
7
  */
7
8
  set(key, value, ttl = _5_MINUTES) {
8
- setTimeout(() => {
9
- this.delete(key);
9
+ const existing = this.timers.get(key);
10
+ if (existing)
11
+ clearTimeout(existing);
12
+ const timer = setTimeout(() => {
13
+ this.timers.delete(key);
14
+ super.delete(key);
10
15
  }, ttl);
16
+ if (typeof timer === "object" && timer !== null && "unref" in timer && typeof timer.unref === "function") {
17
+ timer.unref();
18
+ }
19
+ this.timers.set(key, timer);
11
20
  return super.set(key, value);
12
21
  }
22
+ delete(key) {
23
+ const existing = this.timers.get(key);
24
+ if (existing) {
25
+ clearTimeout(existing);
26
+ this.timers.delete(key);
27
+ }
28
+ return super.delete(key);
29
+ }
13
30
  }
14
31
  export const createContext = (apiType = ZALO_PROTOCOL.API_TYPE, apiVersion = ZALO_PROTOCOL.API_VERSION) => ({
15
32
  API_TYPE: apiType,
@@ -20,6 +37,7 @@ export const createContext = (apiType = ZALO_PROTOCOL.API_TYPE, apiVersion = ZAL
20
37
  checkUpdate: ZALO_DEFAULTS.OPTIONS.CHECK_UPDATE,
21
38
  logging: ZALO_DEFAULTS.OPTIONS.LOGGING,
22
39
  polyfill: global.fetch,
40
+ requestTimeout: 30000,
23
41
  },
24
42
  secretKey: null,
25
43
  });
@@ -1,3 +1,10 @@
1
+ export declare const DEFAULT_BIGINT_ID_FIELDS: readonly ["owner_id", "group_id", "uidFrom", "uidTo", "uid", "ownerId", "destId", "srcId", "fromUid", "threadId", "globalMsgId", "global_msg_id", "globalDelMsgId", "msgId", "refMessageId", "cliMsgId", "client_msg_id", "actionId", "lastActionIdOther", "lastActionId", "resetLastActionId", "lastId", "ids", "topicId", "reminderId", "eventId"];
2
+ /**
3
+ * Tiền xử lý chuỗi JSON trước khi `JSON.parse` (tương đương `preParse(e, t)` trong `app.asar` của Zalo PC).
4
+ * Bọc dấu ngoặc kép quanh các trường định danh 64-bit (`owner_id`, `group_id`, hoặc các số nguyên >= 16 chữ số
5
+ * vượt quá `Number.MAX_SAFE_INTEGER`) để tránh mất 3 chữ số cuối của UID/MsgId do làm tròn IEEE-754.
6
+ */
7
+ export declare function preParseJsonIds(jsonStr: string, alwaysStringFields?: readonly string[], bigIntSafeFields?: readonly string[]): string;
1
8
  export declare function decodeBase64ToBuffer(data: string): Buffer;
2
9
  export declare function decodeUnit8Array(data: Uint8Array): string | null;
3
10
  export declare function decodeEventData(parsed: Record<string, unknown>, cipherKey?: string): Promise<any>;
@@ -2,6 +2,68 @@ import crypto from "node:crypto";
2
2
  import pako from "pako";
3
3
  import JSONBig from "json-bigint";
4
4
  import { ZaloApiError } from "../Errors/index.js";
5
+ const jsonBigString = JSONBig({ storeAsString: true });
6
+ export const DEFAULT_BIGINT_ID_FIELDS = [
7
+ "owner_id",
8
+ "group_id",
9
+ "uidFrom",
10
+ "uidTo",
11
+ "uid",
12
+ "ownerId",
13
+ "destId",
14
+ "srcId",
15
+ "fromUid",
16
+ "threadId",
17
+ "globalMsgId",
18
+ "global_msg_id",
19
+ "globalDelMsgId",
20
+ "msgId",
21
+ "refMessageId",
22
+ "cliMsgId",
23
+ "client_msg_id",
24
+ "actionId",
25
+ "lastActionIdOther",
26
+ "lastActionId",
27
+ "resetLastActionId",
28
+ "lastId",
29
+ "ids",
30
+ "topicId",
31
+ "reminderId",
32
+ "eventId",
33
+ ];
34
+ /**
35
+ * Tiền xử lý chuỗi JSON trước khi `JSON.parse` (tương đương `preParse(e, t)` trong `app.asar` của Zalo PC).
36
+ * Bọc dấu ngoặc kép quanh các trường định danh 64-bit (`owner_id`, `group_id`, hoặc các số nguyên >= 16 chữ số
37
+ * vượt quá `Number.MAX_SAFE_INTEGER`) để tránh mất 3 chữ số cuối của UID/MsgId do làm tròn IEEE-754.
38
+ */
39
+ export function preParseJsonIds(jsonStr, alwaysStringFields = ["owner_id", "group_id"], bigIntSafeFields = DEFAULT_BIGINT_ID_FIELDS) {
40
+ if (!jsonStr || typeof jsonStr !== "string")
41
+ return jsonStr;
42
+ const alwaysSet = new Set(alwaysStringFields);
43
+ const allFields = Array.from(new Set([...alwaysStringFields, ...bigIntSafeFields]));
44
+ if (allFields.length === 0)
45
+ return jsonStr;
46
+ const escapedKeys = allFields.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
47
+ const keyValueRegex = new RegExp(`"(${escapedKeys})"(\\s*:\\s*)(\\d+|\\[\\s*\\d+(?:\\s*,\\s*\\d+)*\\s*\\])`, "g");
48
+ return jsonStr.replace(keyValueRegex, (fullMatch, key, sep, rawVal) => {
49
+ const forceString = alwaysSet.has(key);
50
+ if (rawVal.startsWith("[")) {
51
+ const inner = rawVal.slice(1, -1);
52
+ const parts = inner.split(",").map((part) => {
53
+ const trimmed = part.trim();
54
+ if (forceString || trimmed.length >= 16 || Number(trimmed) > Number.MAX_SAFE_INTEGER) {
55
+ return `"${trimmed}"`;
56
+ }
57
+ return trimmed;
58
+ });
59
+ return `"${key}"${sep}[${parts.join(",")}]`;
60
+ }
61
+ if (forceString || rawVal.length >= 16 || Number(rawVal) > Number.MAX_SAFE_INTEGER) {
62
+ return `"${key}"${sep}"${rawVal}"`;
63
+ }
64
+ return fullMatch;
65
+ });
66
+ }
5
67
  export function decodeBase64ToBuffer(data) {
6
68
  return Buffer.from(data, "base64");
7
69
  }
@@ -23,7 +85,7 @@ export async function decodeEventData(parsed, cipherKey) {
23
85
  const rawData = parsed.data;
24
86
  const encryptType = parsed.encrypt;
25
87
  if (encryptType === 0)
26
- return JSON.parse(rawData);
88
+ return jsonBigString.parse(preParseJsonIds(rawData));
27
89
  const decodedBuffer = decodeBase64ToBuffer(encryptType === 1 ? rawData : decodeURIComponent(rawData));
28
90
  let decryptedBuffer = decodedBuffer;
29
91
  if (encryptType !== 1) {
@@ -48,5 +110,5 @@ export async function decodeEventData(parsed, cipherKey) {
48
110
  const decodedData = decodeUnit8Array(decompressedBuffer);
49
111
  if (!decodedData)
50
112
  return;
51
- return JSONBig.parse(decodedData);
113
+ return jsonBigString.parse(preParseJsonIds(decodedData));
52
114
  }