@softtynet/zca-js 2.3.4 → 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 (123) hide show
  1. package/README.md +47 -11
  2. package/dist/apis/call/addUserGroupCall.d.ts +11 -0
  3. package/dist/apis/call/addUserGroupCall.js +48 -0
  4. package/dist/apis/call/answerAckVoiceCall.d.ts +5 -0
  5. package/dist/apis/call/answerAckVoiceCall.js +38 -0
  6. package/dist/apis/call/answerGroupCall.d.ts +10 -0
  7. package/dist/apis/call/answerGroupCall.js +45 -0
  8. package/dist/apis/call/answerVoiceCall.d.ts +11 -0
  9. package/dist/apis/call/answerVoiceCall.js +50 -0
  10. package/dist/apis/call/broadcastGroupCallState.d.ts +10 -0
  11. package/dist/apis/call/broadcastGroupCallState.js +45 -0
  12. package/dist/apis/call/cancelGroupCall.d.ts +10 -0
  13. package/dist/apis/call/cancelGroupCall.js +45 -0
  14. package/dist/apis/call/cancelVoiceCall.d.ts +1 -1
  15. package/dist/apis/call/cancelVoiceCall.js +5 -1
  16. package/dist/apis/call/createController.d.ts +7 -0
  17. package/dist/apis/call/createController.js +9 -0
  18. package/dist/apis/call/createRtcSession.d.ts +8 -0
  19. package/dist/apis/call/createRtcSession.js +10 -0
  20. package/dist/apis/call/endGroupCall.d.ts +10 -0
  21. package/dist/apis/call/endGroupCall.js +45 -0
  22. package/dist/apis/call/finishGroupCall.d.ts +12 -0
  23. package/dist/apis/call/finishGroupCall.js +47 -0
  24. package/dist/apis/call/getGroupCallState.d.ts +3 -0
  25. package/dist/apis/call/getGroupCallState.js +38 -0
  26. package/dist/apis/call/holdVoiceCall.d.ts +5 -0
  27. package/dist/apis/call/holdVoiceCall.js +34 -0
  28. package/dist/apis/call/joinGroupCall.d.ts +11 -0
  29. package/dist/apis/call/joinGroupCall.js +46 -0
  30. package/dist/apis/call/logEndVoiceCall.d.ts +8 -0
  31. package/dist/apis/call/logEndVoiceCall.js +46 -0
  32. package/dist/apis/call/notifyGroupCall.d.ts +9 -0
  33. package/dist/apis/call/notifyGroupCall.js +44 -0
  34. package/dist/apis/call/pingGroupCall.d.ts +10 -0
  35. package/dist/apis/call/pingGroupCall.js +45 -0
  36. package/dist/apis/call/requestAudioToVideoCall.d.ts +5 -0
  37. package/dist/apis/call/requestAudioToVideoCall.js +37 -0
  38. package/dist/apis/call/requestGroupCall.d.ts +9 -0
  39. package/dist/apis/call/requestGroupCall.js +46 -0
  40. package/dist/apis/call/requestShareScreenCall.d.ts +5 -0
  41. package/dist/apis/call/requestShareScreenCall.js +37 -0
  42. package/dist/apis/call/requestVoiceCall.d.ts +10 -0
  43. package/dist/apis/call/requestVoiceCall.js +47 -0
  44. package/dist/apis/call/ringRingGroupCall.d.ts +10 -0
  45. package/dist/apis/call/ringRingGroupCall.js +45 -0
  46. package/dist/apis/call/ringRingVoiceCall.d.ts +5 -0
  47. package/dist/apis/call/ringRingVoiceCall.js +38 -0
  48. package/dist/apis/call/sendRequestGroupCall.d.ts +11 -0
  49. package/dist/apis/call/sendRequestGroupCall.js +48 -0
  50. package/dist/apis/call/sendRequestVoiceCall.d.ts +11 -0
  51. package/dist/apis/call/sendRequestVoiceCall.js +51 -0
  52. package/dist/apis/call/sendVoiceCall.d.ts +8 -1
  53. package/dist/apis/call/sendVoiceCall.js +10 -8
  54. package/dist/apis/call/shareScreenGroupCall.d.ts +10 -0
  55. package/dist/apis/call/shareScreenGroupCall.js +45 -0
  56. package/dist/apis/call/unholdVoiceCall.d.ts +5 -0
  57. package/dist/apis/call/unholdVoiceCall.js +34 -0
  58. package/dist/apis/call/updateCallMediaState.d.ts +7 -0
  59. package/dist/apis/call/updateCallMediaState.js +61 -0
  60. package/dist/apis.d.ts +81 -0
  61. package/dist/apis.js +135 -0
  62. package/dist/call-engine/ZRTPPacketCodec.d.ts +262 -0
  63. package/dist/call-engine/ZRTPPacketCodec.js +506 -0
  64. package/dist/call-engine/ZaloCallController.d.ts +134 -0
  65. package/dist/call-engine/ZaloCallController.js +638 -0
  66. package/dist/call-engine/ZaloCallTransport.d.ts +83 -0
  67. package/dist/call-engine/ZaloCallTransport.js +233 -0
  68. package/dist/call-engine/ZaloRtcSession.d.ts +609 -0
  69. package/dist/call-engine/ZaloRtcSession.js +2931 -0
  70. package/dist/call-engine/ZaloSrtpSession.d.ts +80 -0
  71. package/dist/call-engine/ZaloSrtpSession.js +278 -0
  72. package/dist/call-engine/index.d.ts +18 -0
  73. package/dist/call-engine/index.js +24 -0
  74. package/dist/cjs/apis/call/addUserGroupCall.cjs +53 -0
  75. package/dist/cjs/apis/call/answerAckVoiceCall.cjs +43 -0
  76. package/dist/cjs/apis/call/answerGroupCall.cjs +50 -0
  77. package/dist/cjs/apis/call/answerVoiceCall.cjs +55 -0
  78. package/dist/cjs/apis/call/broadcastGroupCallState.cjs +50 -0
  79. package/dist/cjs/apis/call/cancelGroupCall.cjs +50 -0
  80. package/dist/cjs/apis/call/cancelVoiceCall.cjs +5 -1
  81. package/dist/cjs/apis/call/createController.cjs +14 -0
  82. package/dist/cjs/apis/call/createRtcSession.cjs +15 -0
  83. package/dist/cjs/apis/call/endGroupCall.cjs +50 -0
  84. package/dist/cjs/apis/call/finishGroupCall.cjs +52 -0
  85. package/dist/cjs/apis/call/getGroupCallState.cjs +43 -0
  86. package/dist/cjs/apis/call/holdVoiceCall.cjs +39 -0
  87. package/dist/cjs/apis/call/joinGroupCall.cjs +51 -0
  88. package/dist/cjs/apis/call/logEndVoiceCall.cjs +51 -0
  89. package/dist/cjs/apis/call/notifyGroupCall.cjs +49 -0
  90. package/dist/cjs/apis/call/pingGroupCall.cjs +50 -0
  91. package/dist/cjs/apis/call/requestAudioToVideoCall.cjs +42 -0
  92. package/dist/cjs/apis/call/requestGroupCall.cjs +51 -0
  93. package/dist/cjs/apis/call/requestShareScreenCall.cjs +42 -0
  94. package/dist/cjs/apis/call/requestVoiceCall.cjs +52 -0
  95. package/dist/cjs/apis/call/ringRingGroupCall.cjs +50 -0
  96. package/dist/cjs/apis/call/ringRingVoiceCall.cjs +43 -0
  97. package/dist/cjs/apis/call/sendRequestGroupCall.cjs +53 -0
  98. package/dist/cjs/apis/call/sendRequestVoiceCall.cjs +56 -0
  99. package/dist/cjs/apis/call/sendVoiceCall.cjs +10 -8
  100. package/dist/cjs/apis/call/shareScreenGroupCall.cjs +50 -0
  101. package/dist/cjs/apis/call/unholdVoiceCall.cjs +39 -0
  102. package/dist/cjs/apis/call/updateCallMediaState.cjs +66 -0
  103. package/dist/cjs/apis.cjs +135 -0
  104. package/dist/cjs/call-engine/ZRTPPacketCodec.cjs +525 -0
  105. package/dist/cjs/call-engine/ZaloCallController.cjs +643 -0
  106. package/dist/cjs/call-engine/ZaloCallTransport.cjs +239 -0
  107. package/dist/cjs/call-engine/ZaloRtcSession.cjs +2937 -0
  108. package/dist/cjs/call-engine/ZaloSrtpSession.cjs +287 -0
  109. package/dist/cjs/constants/protocol.cjs +1 -1
  110. package/dist/cjs/index.cjs +61 -11
  111. package/dist/cjs/models/CallEvent.cjs +95 -1
  112. package/dist/cjs/realtime/handlers/control.handler.cjs +17 -3
  113. package/dist/cjs/realtime/listener.cjs +19 -0
  114. package/dist/constants/protocol.d.ts +1 -1
  115. package/dist/constants/protocol.js +1 -1
  116. package/dist/index.d.ts +32 -4
  117. package/dist/index.js +2 -0
  118. package/dist/models/CallEvent.d.ts +15 -0
  119. package/dist/models/CallEvent.js +95 -1
  120. package/dist/realtime/handlers/control.handler.js +17 -3
  121. package/dist/realtime/listener.d.ts +5 -0
  122. package/dist/realtime/listener.js +19 -0
  123. package/package.json +2 -1
@@ -0,0 +1,287 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('node:crypto');
4
+ var node_buffer = require('node:buffer');
5
+ var ZaloApiError = require('../Errors/ZaloApiError.cjs');
6
+
7
+ /**
8
+ * Sinh ngẫu nhiên khoá Master Key (16 bytes) + Master Salt (14 bytes) = 30 bytes,
9
+ * mã hoá thành chuỗi Base64 40 ký tự chuẩn `zrtc::SrtpTransport` của Zalo.
10
+ */
11
+ function generateSrtpMasterKeyBase64() {
12
+ return crypto.randomBytes(30).toString("base64");
13
+ }
14
+ /**
15
+ * Đóng gói gói tin chuẩn WebRTC RTP (12-byte header + payload).
16
+ */
17
+ function buildRtpPacket(options) {
18
+ const header = node_buffer.Buffer.allocUnsafe(12);
19
+ header.writeUInt8(0x80, 0); // Version = 2, P = 0, X = 0, CC = 0
20
+ const ptByte = ((options.marker ? 0x80 : 0x00) | ((options.payloadType ?? 112) & 0x7f)) & 0xff;
21
+ header.writeUInt8(ptByte, 1);
22
+ header.writeUInt16BE(options.sequenceNumber & 0xffff, 2);
23
+ header.writeUInt32BE(options.timestamp >>> 0, 4);
24
+ header.writeUInt32BE(options.ssrc >>> 0, 8);
25
+ return node_buffer.Buffer.concat([header, options.payload]);
26
+ }
27
+ /**
28
+ * Phân tích gói tin chuẩn WebRTC RTP (RFC 3550).
29
+ */
30
+ function parseRtpPacket(packet) {
31
+ if (!packet || packet.length < 12) {
32
+ throw new ZaloApiError.ZaloApiError(`Invalid RTP packet length: ${packet?.length ?? 0}`);
33
+ }
34
+ const firstByte = packet.readUInt8(0);
35
+ const secondByte = packet.readUInt8(1);
36
+ const version = (firstByte >> 6) & 0x03;
37
+ const padding = ((firstByte >> 5) & 0x01) === 1;
38
+ const extension = ((firstByte >> 4) & 0x01) === 1;
39
+ const csrcCount = firstByte & 0x0f;
40
+ const marker = ((secondByte >> 7) & 0x01) === 1;
41
+ const payloadType = secondByte & 0x7f;
42
+ const sequenceNumber = packet.readUInt16BE(2);
43
+ const timestamp = packet.readUInt32BE(4);
44
+ const ssrc = packet.readUInt32BE(8);
45
+ let headerLength = 12 + csrcCount * 4;
46
+ if (packet.length < headerLength) {
47
+ throw new ZaloApiError.ZaloApiError("RTP packet shorter than CSRC header length");
48
+ }
49
+ if (extension) {
50
+ if (packet.length < headerLength + 4) {
51
+ throw new ZaloApiError.ZaloApiError("RTP packet shorter than extension header");
52
+ }
53
+ const extLenWords = packet.readUInt16BE(headerLength + 2);
54
+ headerLength += 4 + extLenWords * 4;
55
+ if (packet.length < headerLength) {
56
+ throw new ZaloApiError.ZaloApiError("RTP packet shorter than extension payload");
57
+ }
58
+ }
59
+ let payloadEnd = packet.length;
60
+ if (padding && packet.length > headerLength) {
61
+ const paddingLen = packet.readUInt8(packet.length - 1);
62
+ if (paddingLen > 0 && headerLength + paddingLen <= packet.length) {
63
+ payloadEnd = packet.length - paddingLen;
64
+ }
65
+ }
66
+ return {
67
+ version,
68
+ padding,
69
+ extension,
70
+ csrcCount,
71
+ marker,
72
+ payloadType,
73
+ sequenceNumber,
74
+ timestamp,
75
+ ssrc,
76
+ headerLength,
77
+ header: packet.subarray(0, headerLength),
78
+ payload: packet.subarray(headerLength, payloadEnd),
79
+ };
80
+ }
81
+ /**
82
+ * Thuật toán dẫn xuất khoá phiên SRTP/SRTCP theo RFC 3711 Mục 4.3.1 (AES-CM PRF).
83
+ */
84
+ function deriveSrtpSessionKey(masterKey, masterSalt, label, outByteLength) {
85
+ const iv = node_buffer.Buffer.alloc(16, 0);
86
+ masterSalt.copy(iv, 0, 0, 14);
87
+ // label nằm tại byte thứ 7 (tức (label << 48) trong khối 112-bit salt)
88
+ iv[7] ^= label & 0xff;
89
+ const cipher = crypto.createCipheriv("aes-128-ctr", masterKey, iv);
90
+ const zeroBlock = node_buffer.Buffer.alloc(outByteLength, 0);
91
+ return node_buffer.Buffer.concat([cipher.update(zeroBlock), cipher.final()]).subarray(0, outByteLength);
92
+ }
93
+ function buildSrtpCtrIv(sessionSalt, ssrc, roc, seqNum) {
94
+ const iv = node_buffer.Buffer.alloc(16, 0);
95
+ sessionSalt.copy(iv, 0, 0, 14);
96
+ const ssrcBuf = node_buffer.Buffer.allocUnsafe(4);
97
+ ssrcBuf.writeUInt32BE(ssrc >>> 0, 0);
98
+ for (let i = 0; i < 4; i++) {
99
+ iv[4 + i] ^= ssrcBuf[i];
100
+ }
101
+ const rocBuf = node_buffer.Buffer.allocUnsafe(4);
102
+ rocBuf.writeUInt32BE(roc >>> 0, 0);
103
+ for (let i = 0; i < 4; i++) {
104
+ iv[8 + i] ^= rocBuf[i];
105
+ }
106
+ iv[12] ^= (seqNum >>> 8) & 0xff;
107
+ iv[13] ^= seqNum & 0xff;
108
+ return iv;
109
+ }
110
+ function buildSrtcpCtrIv(sessionSalt, ssrc, srtcpIndex) {
111
+ const iv = node_buffer.Buffer.alloc(16, 0);
112
+ sessionSalt.copy(iv, 0, 0, 14);
113
+ const ssrcBuf = node_buffer.Buffer.allocUnsafe(4);
114
+ ssrcBuf.writeUInt32BE(ssrc >>> 0, 0);
115
+ for (let i = 0; i < 4; i++) {
116
+ iv[4 + i] ^= ssrcBuf[i];
117
+ }
118
+ const idxBuf = node_buffer.Buffer.allocUnsafe(4);
119
+ idxBuf.writeUInt32BE((srtcpIndex & 0x7fffffff) >>> 0, 0);
120
+ for (let i = 0; i < 4; i++) {
121
+ iv[10 + i] ^= idxBuf[i];
122
+ }
123
+ return iv;
124
+ }
125
+ /**
126
+ * Phiên mã hoá / giải mã SRTP & SRTCP thuần Node.js (`node:crypto`)
127
+ * tương thích 100% với `zrtc::SrtpTransport` (`AES_CM_128_HMAC_SHA1_80` / `AES_CM_128_HMAC_SHA1_32`).
128
+ */
129
+ class ZaloSrtpSession {
130
+ masterKey;
131
+ masterSalt;
132
+ masterKeyBase64;
133
+ suite;
134
+ tagLength;
135
+ rtpKey;
136
+ rtpAuthKey;
137
+ rtpSalt;
138
+ rtcpKey;
139
+ rtcpAuthKey;
140
+ rtcpSalt;
141
+ sendRoc = 0;
142
+ recvRoc = 0;
143
+ lastSendSeq = -1;
144
+ lastRecvSeq = -1;
145
+ srtcpIndex = 0;
146
+ constructor(masterKeyInput, suite = "AES_CM_128_HMAC_SHA1_80") {
147
+ const raw = typeof masterKeyInput === "string"
148
+ ? node_buffer.Buffer.from(masterKeyInput, "base64")
149
+ : node_buffer.Buffer.from(masterKeyInput);
150
+ if (raw.length < 30) {
151
+ throw new ZaloApiError.ZaloApiError(`SRTP master key + salt must be at least 30 bytes (got ${raw.length} bytes)`);
152
+ }
153
+ this.masterKey = raw.subarray(0, 16);
154
+ this.masterSalt = raw.subarray(16, 30);
155
+ this.masterKeyBase64 = raw.subarray(0, 30).toString("base64");
156
+ this.suite = suite;
157
+ this.tagLength = suite === "AES_CM_128_HMAC_SHA1_32" ? 4 : 10;
158
+ this.rtpKey = deriveSrtpSessionKey(this.masterKey, this.masterSalt, 0x00, 16);
159
+ this.rtpAuthKey = deriveSrtpSessionKey(this.masterKey, this.masterSalt, 0x01, 20);
160
+ this.rtpSalt = deriveSrtpSessionKey(this.masterKey, this.masterSalt, 0x02, 14);
161
+ this.rtcpKey = deriveSrtpSessionKey(this.masterKey, this.masterSalt, 0x03, 16);
162
+ this.rtcpAuthKey = deriveSrtpSessionKey(this.masterKey, this.masterSalt, 0x04, 20);
163
+ this.rtcpSalt = deriveSrtpSessionKey(this.masterKey, this.masterSalt, 0x05, 14);
164
+ }
165
+ /**
166
+ * Mã hoá gói tin RTP thành SRTP (AES-128-CTR + HMAC-SHA1 tag).
167
+ */
168
+ protectRtp(rtpPacket, explicitRoc) {
169
+ const parsed = parseRtpPacket(rtpPacket);
170
+ let roc = explicitRoc ?? this.sendRoc;
171
+ if (explicitRoc === undefined) {
172
+ if (this.lastSendSeq !== -1 && parsed.sequenceNumber < this.lastSendSeq && this.lastSendSeq - parsed.sequenceNumber > 0x8000) {
173
+ this.sendRoc = (this.sendRoc + 1) >>> 0;
174
+ roc = this.sendRoc;
175
+ }
176
+ this.lastSendSeq = parsed.sequenceNumber;
177
+ }
178
+ const iv = buildSrtpCtrIv(this.rtpSalt, parsed.ssrc, roc, parsed.sequenceNumber);
179
+ const cipher = crypto.createCipheriv("aes-128-ctr", this.rtpKey, iv);
180
+ const encryptedPayload = node_buffer.Buffer.concat([cipher.update(parsed.payload), cipher.final()]);
181
+ const rocBuf = node_buffer.Buffer.allocUnsafe(4);
182
+ rocBuf.writeUInt32BE(roc >>> 0, 0);
183
+ const hmac = crypto.createHmac("sha1", this.rtpAuthKey);
184
+ hmac.update(parsed.header);
185
+ hmac.update(encryptedPayload);
186
+ hmac.update(rocBuf);
187
+ const authTag = hmac.digest().subarray(0, this.tagLength);
188
+ return node_buffer.Buffer.concat([parsed.header, encryptedPayload, authTag]);
189
+ }
190
+ /**
191
+ * Xác thực HMAC-SHA1 và giải mã gói tin SRTP về RTP thuần.
192
+ */
193
+ unprotectRtp(srtpPacket, explicitRoc) {
194
+ if (!srtpPacket || srtpPacket.length < 12 + this.tagLength) {
195
+ throw new ZaloApiError.ZaloApiError(`Invalid SRTP packet length: ${srtpPacket?.length ?? 0}`);
196
+ }
197
+ const rtpPortion = srtpPacket.subarray(0, srtpPacket.length - this.tagLength);
198
+ const receivedTag = srtpPacket.subarray(srtpPacket.length - this.tagLength);
199
+ const parsed = parseRtpPacket(rtpPortion);
200
+ let roc = explicitRoc ?? this.recvRoc;
201
+ if (explicitRoc === undefined && this.lastRecvSeq !== -1) {
202
+ if (parsed.sequenceNumber < this.lastRecvSeq && this.lastRecvSeq - parsed.sequenceNumber > 0x8000) {
203
+ roc = (this.recvRoc + 1) >>> 0;
204
+ }
205
+ }
206
+ const rocBuf = node_buffer.Buffer.allocUnsafe(4);
207
+ rocBuf.writeUInt32BE(roc >>> 0, 0);
208
+ const hmac = crypto.createHmac("sha1", this.rtpAuthKey);
209
+ hmac.update(parsed.header);
210
+ hmac.update(parsed.payload);
211
+ hmac.update(rocBuf);
212
+ const expectedTag = hmac.digest().subarray(0, this.tagLength);
213
+ if (!crypto.timingSafeEqual(receivedTag, expectedTag)) {
214
+ throw new ZaloApiError.ZaloApiError("SRTP authentication tag verification failed");
215
+ }
216
+ if (explicitRoc === undefined) {
217
+ this.recvRoc = roc;
218
+ this.lastRecvSeq = parsed.sequenceNumber;
219
+ }
220
+ const iv = buildSrtpCtrIv(this.rtpSalt, parsed.ssrc, roc, parsed.sequenceNumber);
221
+ const decipher = crypto.createDecipheriv("aes-128-ctr", this.rtpKey, iv);
222
+ const decryptedPayload = node_buffer.Buffer.concat([decipher.update(parsed.payload), decipher.final()]);
223
+ return node_buffer.Buffer.concat([parsed.header, decryptedPayload]);
224
+ }
225
+ /**
226
+ * Mã hoá gói tin RTCP thành SRTCP (RFC 3711 Mục 3.4).
227
+ */
228
+ protectRtcp(rtcpPacket) {
229
+ if (!rtcpPacket || rtcpPacket.length < 8) {
230
+ throw new ZaloApiError.ZaloApiError(`Invalid RTCP packet length: ${rtcpPacket?.length ?? 0}`);
231
+ }
232
+ const header = rtcpPacket.subarray(0, 8);
233
+ const payload = rtcpPacket.subarray(8);
234
+ const ssrc = rtcpPacket.readUInt32BE(4);
235
+ const index = (this.srtcpIndex++ & 0x7fffffff) >>> 0;
236
+ const iv = buildSrtcpCtrIv(this.rtcpSalt, ssrc, index);
237
+ const cipher = crypto.createCipheriv("aes-128-ctr", this.rtcpKey, iv);
238
+ const encryptedPayload = node_buffer.Buffer.concat([cipher.update(payload), cipher.final()]);
239
+ const indexTrailer = node_buffer.Buffer.allocUnsafe(4);
240
+ indexTrailer.writeUInt32BE((0x80000000 | index) >>> 0, 0);
241
+ const hmac = crypto.createHmac("sha1", this.rtcpAuthKey);
242
+ hmac.update(header);
243
+ hmac.update(encryptedPayload);
244
+ hmac.update(indexTrailer);
245
+ const authTag = hmac.digest().subarray(0, this.tagLength);
246
+ return node_buffer.Buffer.concat([header, encryptedPayload, indexTrailer, authTag]);
247
+ }
248
+ /**
249
+ * Xác thực và giải mã gói tin SRTCP về RTCP thuần.
250
+ */
251
+ unprotectRtcp(srtcpPacket) {
252
+ if (!srtcpPacket || srtcpPacket.length < 8 + 4 + this.tagLength) {
253
+ throw new ZaloApiError.ZaloApiError(`Invalid SRTCP packet length: ${srtcpPacket?.length ?? 0}`);
254
+ }
255
+ const tagStart = srtcpPacket.length - this.tagLength;
256
+ const indexStart = tagStart - 4;
257
+ const header = srtcpPacket.subarray(0, 8);
258
+ const encryptedPayload = srtcpPacket.subarray(8, indexStart);
259
+ const indexTrailer = srtcpPacket.subarray(indexStart, tagStart);
260
+ const receivedTag = srtcpPacket.subarray(tagStart);
261
+ const hmac = crypto.createHmac("sha1", this.rtcpAuthKey);
262
+ hmac.update(header);
263
+ hmac.update(encryptedPayload);
264
+ hmac.update(indexTrailer);
265
+ const expectedTag = hmac.digest().subarray(0, this.tagLength);
266
+ if (!crypto.timingSafeEqual(receivedTag, expectedTag)) {
267
+ throw new ZaloApiError.ZaloApiError("SRTCP authentication tag verification failed");
268
+ }
269
+ const eAndIndex = indexTrailer.readUInt32BE(0);
270
+ const isEncrypted = (eAndIndex & 0x80000000) !== 0;
271
+ const index = eAndIndex & 0x7fffffff;
272
+ if (!isEncrypted) {
273
+ return node_buffer.Buffer.concat([header, encryptedPayload]);
274
+ }
275
+ const ssrc = header.readUInt32BE(4);
276
+ const iv = buildSrtcpCtrIv(this.rtcpSalt, ssrc, index);
277
+ const decipher = crypto.createDecipheriv("aes-128-ctr", this.rtcpKey, iv);
278
+ const decryptedPayload = node_buffer.Buffer.concat([decipher.update(encryptedPayload), decipher.final()]);
279
+ return node_buffer.Buffer.concat([header, decryptedPayload]);
280
+ }
281
+ }
282
+
283
+ exports.ZaloSrtpSession = ZaloSrtpSession;
284
+ exports.buildRtpPacket = buildRtpPacket;
285
+ exports.deriveSrtpSessionKey = deriveSrtpSessionKey;
286
+ exports.generateSrtpMasterKeyBase64 = generateSrtpMasterKeyBase64;
287
+ exports.parseRtpPacket = parseRtpPacket;
@@ -12,7 +12,7 @@ const ZALO_PROTOCOL = {
12
12
  API_VERSION: 689,
13
13
  DEFAULT_API_VERSION: 689,
14
14
  /** Phiên bản SDK hiện tại */
15
- SDK_VERSION: "2.3.4",
15
+ SDK_VERSION: "2.3.5",
16
16
  /** Mã zpw_type đặc thù dành riêng cho phân hệ Gọi Thoại & Video (binary C++ ZaloCall) */
17
17
  VOICECALL_API_TYPE: 24,
18
18
  /** Phiên bản mã hóa ZCID (ParamDecryptor) */
@@ -32,17 +32,9 @@ var zdbCipher = require('./crypto/zdb-cipher.cjs');
32
32
  var wsCipher = require('./crypto/ws-cipher.cjs');
33
33
  var serviceRouter = require('./core/service-router.cjs');
34
34
  var zcloudClient = require('./core/zcloud-client.cjs');
35
- var zalo = require('./zalo.cjs');
36
35
  var types = require('./realtime/types.cjs');
37
- require('events');
38
- require('ws');
39
- require('crypto-js');
40
- require('node:crypto');
41
- require('node:fs');
42
- require('node:path');
43
- require('spark-md5');
44
- require('tough-cookie');
45
- require('./context.cjs');
36
+ var listener = require('./realtime/listener.cjs');
37
+ var zalo = require('./zalo.cjs');
46
38
  var loginQR = require('./apis/loginQR.cjs');
47
39
  var getFriendRecommendations = require('./apis/friend/getFriendRecommendations.cjs');
48
40
  var reviewPendingMemberRequest = require('./apis/group/reviewPendingMemberRequest.cjs');
@@ -53,6 +45,18 @@ var updateAutoDeleteChat = require('./apis/chat/updateAutoDeleteChat.cjs');
53
45
  var updateLang = require('./apis/auth/updateLang.cjs');
54
46
  var updateSettings = require('./apis/profile/updateSettings.cjs');
55
47
  var sendVoiceCall = require('./apis/call/sendVoiceCall.cjs');
48
+ var ZaloCallController = require('./call-engine/ZaloCallController.cjs');
49
+ var ZaloRtcSession = require('./call-engine/ZaloRtcSession.cjs');
50
+ var ZRTPPacketCodec = require('./call-engine/ZRTPPacketCodec.cjs');
51
+ var ZaloSrtpSession = require('./call-engine/ZaloSrtpSession.cjs');
52
+ var ZaloCallTransport = require('./call-engine/ZaloCallTransport.cjs');
53
+ require('crypto-js');
54
+ require('node:crypto');
55
+ require('node:fs');
56
+ require('node:path');
57
+ require('spark-md5');
58
+ require('tough-cookie');
59
+ require('./context.cjs');
56
60
  var apis = require('./apis.cjs');
57
61
 
58
62
 
@@ -168,11 +172,12 @@ exports.getServiceEndpoint = serviceRouter.getServiceEndpoint;
168
172
  exports.getServiceEndpoints = serviceRouter.getServiceEndpoints;
169
173
  exports.requestZCloudDecrypted = zcloudClient.requestZCloudDecrypted;
170
174
  exports.requestZFamilyDecrypted = zcloudClient.requestZFamilyDecrypted;
171
- exports.Zalo = zalo.Zalo;
172
175
  Object.defineProperty(exports, "CloseReason", {
173
176
  enumerable: true,
174
177
  get: function () { return types.CloseReason; }
175
178
  });
179
+ exports.Listener = listener.Listener;
180
+ exports.Zalo = zalo.Zalo;
176
181
  Object.defineProperty(exports, "LoginQRCallbackEventType", {
177
182
  enumerable: true,
178
183
  get: function () { return loginQR.LoginQRCallbackEventType; }
@@ -221,4 +226,49 @@ Object.defineProperty(exports, "CallType", {
221
226
  enumerable: true,
222
227
  get: function () { return sendVoiceCall.CallType; }
223
228
  });
229
+ exports.ZaloCallController = ZaloCallController.ZaloCallController;
230
+ exports.ZaloRtcSession = ZaloRtcSession.ZaloRtcSession;
231
+ exports.parseIncomingCallEvent = ZaloRtcSession.parseIncomingCallEvent;
232
+ exports.DEFAULT_ZALO_AUDIO_CODEC = ZRTPPacketCodec.DEFAULT_ZALO_AUDIO_CODEC;
233
+ exports.DEFAULT_ZALO_VIDEO_CODEC = ZRTPPacketCodec.DEFAULT_ZALO_VIDEO_CODEC;
234
+ Object.defineProperty(exports, "ZRTCMediaSubCommand", {
235
+ enumerable: true,
236
+ get: function () { return ZRTPPacketCodec.ZRTCMediaSubCommand; }
237
+ });
238
+ Object.defineProperty(exports, "ZRTCRole", {
239
+ enumerable: true,
240
+ get: function () { return ZRTPPacketCodec.ZRTCRole; }
241
+ });
242
+ Object.defineProperty(exports, "ZRTPControlCommand", {
243
+ enumerable: true,
244
+ get: function () { return ZRTPPacketCodec.ZRTPControlCommand; }
245
+ });
246
+ Object.defineProperty(exports, "ZRTPForwardSubCommand", {
247
+ enumerable: true,
248
+ get: function () { return ZRTPPacketCodec.ZRTPForwardSubCommand; }
249
+ });
250
+ Object.defineProperty(exports, "ZRTPLegacyMediaType", {
251
+ enumerable: true,
252
+ get: function () { return ZRTPPacketCodec.ZRTPLegacyMediaType; }
253
+ });
254
+ exports.buildEchoRequest = ZRTPPacketCodec.buildEchoRequest;
255
+ exports.buildEndCallRequest = ZRTPPacketCodec.buildEndCallRequest;
256
+ exports.buildInitCallRequest = ZRTPPacketCodec.buildInitCallRequest;
257
+ exports.buildInitCallResponse = ZRTPPacketCodec.buildInitCallResponse;
258
+ exports.buildPingRequest = ZRTPPacketCodec.buildPingRequest;
259
+ exports.buildPingResponse = ZRTPPacketCodec.buildPingResponse;
260
+ exports.buildRelayPingRequest = ZRTPPacketCodec.buildRelayPingRequest;
261
+ exports.buildRequestForwardPacket = ZRTPPacketCodec.buildRequestForwardPacket;
262
+ exports.buildZrtcExtendData = ZRTPPacketCodec.buildZrtcExtendData;
263
+ exports.parseZRTPPacket = ZRTPPacketCodec.parseZRTPPacket;
264
+ exports.uidToUint32 = ZRTPPacketCodec.uidToUint32;
265
+ exports.wrapLegacyMediaPacket = ZRTPPacketCodec.wrapLegacyMediaPacket;
266
+ exports.wrapZrtcMediaPacket = ZRTPPacketCodec.wrapZrtcMediaPacket;
267
+ exports.ZaloSrtpSession = ZaloSrtpSession.ZaloSrtpSession;
268
+ exports.buildRtpPacket = ZaloSrtpSession.buildRtpPacket;
269
+ exports.deriveSrtpSessionKey = ZaloSrtpSession.deriveSrtpSessionKey;
270
+ exports.generateSrtpMasterKeyBase64 = ZaloSrtpSession.generateSrtpMasterKeyBase64;
271
+ exports.parseRtpPacket = ZaloSrtpSession.parseRtpPacket;
272
+ exports.DEFAULT_ZALOCALL_IPC_KEY_BASE64 = ZaloCallTransport.DEFAULT_ZALOCALL_IPC_KEY_BASE64;
273
+ exports.ZaloCallTransport = ZaloCallTransport.ZaloCallTransport;
224
274
  exports.API = apis.API;
@@ -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
  }
@@ -269,6 +269,7 @@ class Listener extends EventEmitter {
269
269
  if (version == 1 && cmd == 1 && subCmd == 1 && utils.hasOwn(parsed, "key")) {
270
270
  this.cipherKey = parsed.key;
271
271
  this.emit("cipher_key", this.cipherKey);
272
+ this.requestOfflineVoipControls();
272
273
  if (this.pingInterval)
273
274
  clearInterval(this.pingInterval);
274
275
  const ping = () => {
@@ -374,6 +375,24 @@ class Listener extends EventEmitter {
374
375
  };
375
376
  this.sendWs(payload);
376
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
+ }
377
396
  reset() {
378
397
  this.ws = null;
379
398
  this.cipherKey = undefined;
@@ -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.4";
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.4",
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) */