@softtynet/zca-js 2.3.4 → 2.3.6

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 (129) 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/sync/downloadTransferSync.js +19 -1
  61. package/dist/apis.d.ts +81 -0
  62. package/dist/apis.js +135 -0
  63. package/dist/call-engine/ZRTPPacketCodec.d.ts +262 -0
  64. package/dist/call-engine/ZRTPPacketCodec.js +506 -0
  65. package/dist/call-engine/ZaloCallController.d.ts +134 -0
  66. package/dist/call-engine/ZaloCallController.js +638 -0
  67. package/dist/call-engine/ZaloCallTransport.d.ts +83 -0
  68. package/dist/call-engine/ZaloCallTransport.js +259 -0
  69. package/dist/call-engine/ZaloRtcSession.d.ts +609 -0
  70. package/dist/call-engine/ZaloRtcSession.js +2939 -0
  71. package/dist/call-engine/ZaloSrtpSession.d.ts +80 -0
  72. package/dist/call-engine/ZaloSrtpSession.js +278 -0
  73. package/dist/call-engine/index.d.ts +18 -0
  74. package/dist/call-engine/index.js +24 -0
  75. package/dist/cjs/apis/call/addUserGroupCall.cjs +53 -0
  76. package/dist/cjs/apis/call/answerAckVoiceCall.cjs +43 -0
  77. package/dist/cjs/apis/call/answerGroupCall.cjs +50 -0
  78. package/dist/cjs/apis/call/answerVoiceCall.cjs +55 -0
  79. package/dist/cjs/apis/call/broadcastGroupCallState.cjs +50 -0
  80. package/dist/cjs/apis/call/cancelGroupCall.cjs +50 -0
  81. package/dist/cjs/apis/call/cancelVoiceCall.cjs +5 -1
  82. package/dist/cjs/apis/call/createController.cjs +14 -0
  83. package/dist/cjs/apis/call/createRtcSession.cjs +15 -0
  84. package/dist/cjs/apis/call/endGroupCall.cjs +50 -0
  85. package/dist/cjs/apis/call/finishGroupCall.cjs +52 -0
  86. package/dist/cjs/apis/call/getGroupCallState.cjs +43 -0
  87. package/dist/cjs/apis/call/holdVoiceCall.cjs +39 -0
  88. package/dist/cjs/apis/call/joinGroupCall.cjs +51 -0
  89. package/dist/cjs/apis/call/logEndVoiceCall.cjs +51 -0
  90. package/dist/cjs/apis/call/notifyGroupCall.cjs +49 -0
  91. package/dist/cjs/apis/call/pingGroupCall.cjs +50 -0
  92. package/dist/cjs/apis/call/requestAudioToVideoCall.cjs +42 -0
  93. package/dist/cjs/apis/call/requestGroupCall.cjs +51 -0
  94. package/dist/cjs/apis/call/requestShareScreenCall.cjs +42 -0
  95. package/dist/cjs/apis/call/requestVoiceCall.cjs +52 -0
  96. package/dist/cjs/apis/call/ringRingGroupCall.cjs +50 -0
  97. package/dist/cjs/apis/call/ringRingVoiceCall.cjs +43 -0
  98. package/dist/cjs/apis/call/sendRequestGroupCall.cjs +53 -0
  99. package/dist/cjs/apis/call/sendRequestVoiceCall.cjs +56 -0
  100. package/dist/cjs/apis/call/sendVoiceCall.cjs +10 -8
  101. package/dist/cjs/apis/call/shareScreenGroupCall.cjs +50 -0
  102. package/dist/cjs/apis/call/unholdVoiceCall.cjs +39 -0
  103. package/dist/cjs/apis/call/updateCallMediaState.cjs +66 -0
  104. package/dist/cjs/apis/sync/downloadTransferSync.cjs +19 -1
  105. package/dist/cjs/apis.cjs +135 -0
  106. package/dist/cjs/call-engine/ZRTPPacketCodec.cjs +525 -0
  107. package/dist/cjs/call-engine/ZaloCallController.cjs +643 -0
  108. package/dist/cjs/call-engine/ZaloCallTransport.cjs +265 -0
  109. package/dist/cjs/call-engine/ZaloRtcSession.cjs +2945 -0
  110. package/dist/cjs/call-engine/ZaloSrtpSession.cjs +287 -0
  111. package/dist/cjs/constants/protocol.cjs +1 -1
  112. package/dist/cjs/crypto/aes.cjs +3 -1
  113. package/dist/cjs/crypto/zdb-cipher.cjs +10 -3
  114. package/dist/cjs/index.cjs +61 -11
  115. package/dist/cjs/models/CallEvent.cjs +95 -1
  116. package/dist/cjs/realtime/handlers/control.handler.cjs +17 -3
  117. package/dist/cjs/realtime/listener.cjs +19 -0
  118. package/dist/constants/protocol.d.ts +1 -1
  119. package/dist/constants/protocol.js +1 -1
  120. package/dist/crypto/aes.js +3 -1
  121. package/dist/crypto/zdb-cipher.js +10 -3
  122. package/dist/index.d.ts +32 -4
  123. package/dist/index.js +2 -0
  124. package/dist/models/CallEvent.d.ts +15 -0
  125. package/dist/models/CallEvent.js +95 -1
  126. package/dist/realtime/handlers/control.handler.js +17 -3
  127. package/dist/realtime/listener.d.ts +5 -0
  128. package/dist/realtime/listener.js +19 -0
  129. package/package.json +2 -1
@@ -0,0 +1,80 @@
1
+ import { Buffer } from "node:buffer";
2
+ export type SrtpCryptoSuite = "AES_CM_128_HMAC_SHA1_80" | "AES_CM_128_HMAC_SHA1_32";
3
+ export type ParsedRtpPacket = {
4
+ version: number;
5
+ padding: boolean;
6
+ extension: boolean;
7
+ csrcCount: number;
8
+ marker: boolean;
9
+ payloadType: number;
10
+ sequenceNumber: number;
11
+ timestamp: number;
12
+ ssrc: number;
13
+ headerLength: number;
14
+ header: Buffer;
15
+ payload: Buffer;
16
+ };
17
+ export type BuildRtpPacketOptions = {
18
+ payloadType?: number;
19
+ sequenceNumber: number;
20
+ timestamp: number;
21
+ ssrc: number;
22
+ marker?: boolean;
23
+ payload: Buffer;
24
+ };
25
+ /**
26
+ * Sinh ngẫu nhiên khoá Master Key (16 bytes) + Master Salt (14 bytes) = 30 bytes,
27
+ * mã hoá thành chuỗi Base64 40 ký tự chuẩn `zrtc::SrtpTransport` của Zalo.
28
+ */
29
+ export declare function generateSrtpMasterKeyBase64(): string;
30
+ /**
31
+ * Đóng gói gói tin chuẩn WebRTC RTP (12-byte header + payload).
32
+ */
33
+ export declare function buildRtpPacket(options: BuildRtpPacketOptions): Buffer;
34
+ /**
35
+ * Phân tích gói tin chuẩn WebRTC RTP (RFC 3550).
36
+ */
37
+ export declare function parseRtpPacket(packet: Buffer): ParsedRtpPacket;
38
+ /**
39
+ * Thuật toán dẫn xuất khoá phiên SRTP/SRTCP theo RFC 3711 Mục 4.3.1 (AES-CM PRF).
40
+ */
41
+ export declare function deriveSrtpSessionKey(masterKey: Buffer, masterSalt: Buffer, label: number, outByteLength: number): Buffer;
42
+ /**
43
+ * Phiên mã hoá / giải mã SRTP & SRTCP thuần Node.js (`node:crypto`)
44
+ * tương thích 100% với `zrtc::SrtpTransport` (`AES_CM_128_HMAC_SHA1_80` / `AES_CM_128_HMAC_SHA1_32`).
45
+ */
46
+ export declare class ZaloSrtpSession {
47
+ readonly masterKey: Buffer;
48
+ readonly masterSalt: Buffer;
49
+ readonly masterKeyBase64: string;
50
+ readonly suite: SrtpCryptoSuite;
51
+ readonly tagLength: number;
52
+ readonly rtpKey: Buffer;
53
+ readonly rtpAuthKey: Buffer;
54
+ readonly rtpSalt: Buffer;
55
+ readonly rtcpKey: Buffer;
56
+ readonly rtcpAuthKey: Buffer;
57
+ readonly rtcpSalt: Buffer;
58
+ private sendRoc;
59
+ private recvRoc;
60
+ private lastSendSeq;
61
+ private lastRecvSeq;
62
+ private srtcpIndex;
63
+ constructor(masterKeyInput: string | Buffer, suite?: SrtpCryptoSuite);
64
+ /**
65
+ * Mã hoá gói tin RTP thành SRTP (AES-128-CTR + HMAC-SHA1 tag).
66
+ */
67
+ protectRtp(rtpPacket: Buffer, explicitRoc?: number): Buffer;
68
+ /**
69
+ * Xác thực HMAC-SHA1 và giải mã gói tin SRTP về RTP thuần.
70
+ */
71
+ unprotectRtp(srtpPacket: Buffer, explicitRoc?: number): Buffer;
72
+ /**
73
+ * Mã hoá gói tin RTCP thành SRTCP (RFC 3711 Mục 3.4).
74
+ */
75
+ protectRtcp(rtcpPacket: Buffer): Buffer;
76
+ /**
77
+ * Xác thực và giải mã gói tin SRTCP về RTCP thuần.
78
+ */
79
+ unprotectRtcp(srtcpPacket: Buffer): Buffer;
80
+ }
@@ -0,0 +1,278 @@
1
+ import crypto from "node:crypto";
2
+ import { Buffer } from "node:buffer";
3
+ import { ZaloApiError } from "../Errors/ZaloApiError.js";
4
+ /**
5
+ * Sinh ngẫu nhiên khoá Master Key (16 bytes) + Master Salt (14 bytes) = 30 bytes,
6
+ * mã hoá thành chuỗi Base64 40 ký tự chuẩn `zrtc::SrtpTransport` của Zalo.
7
+ */
8
+ export function generateSrtpMasterKeyBase64() {
9
+ return crypto.randomBytes(30).toString("base64");
10
+ }
11
+ /**
12
+ * Đóng gói gói tin chuẩn WebRTC RTP (12-byte header + payload).
13
+ */
14
+ export function buildRtpPacket(options) {
15
+ const header = Buffer.allocUnsafe(12);
16
+ header.writeUInt8(0x80, 0); // Version = 2, P = 0, X = 0, CC = 0
17
+ const ptByte = ((options.marker ? 0x80 : 0x00) | ((options.payloadType ?? 112) & 0x7f)) & 0xff;
18
+ header.writeUInt8(ptByte, 1);
19
+ header.writeUInt16BE(options.sequenceNumber & 0xffff, 2);
20
+ header.writeUInt32BE(options.timestamp >>> 0, 4);
21
+ header.writeUInt32BE(options.ssrc >>> 0, 8);
22
+ return Buffer.concat([header, options.payload]);
23
+ }
24
+ /**
25
+ * Phân tích gói tin chuẩn WebRTC RTP (RFC 3550).
26
+ */
27
+ export function parseRtpPacket(packet) {
28
+ if (!packet || packet.length < 12) {
29
+ throw new ZaloApiError(`Invalid RTP packet length: ${packet?.length ?? 0}`);
30
+ }
31
+ const firstByte = packet.readUInt8(0);
32
+ const secondByte = packet.readUInt8(1);
33
+ const version = (firstByte >> 6) & 0x03;
34
+ const padding = ((firstByte >> 5) & 0x01) === 1;
35
+ const extension = ((firstByte >> 4) & 0x01) === 1;
36
+ const csrcCount = firstByte & 0x0f;
37
+ const marker = ((secondByte >> 7) & 0x01) === 1;
38
+ const payloadType = secondByte & 0x7f;
39
+ const sequenceNumber = packet.readUInt16BE(2);
40
+ const timestamp = packet.readUInt32BE(4);
41
+ const ssrc = packet.readUInt32BE(8);
42
+ let headerLength = 12 + csrcCount * 4;
43
+ if (packet.length < headerLength) {
44
+ throw new ZaloApiError("RTP packet shorter than CSRC header length");
45
+ }
46
+ if (extension) {
47
+ if (packet.length < headerLength + 4) {
48
+ throw new ZaloApiError("RTP packet shorter than extension header");
49
+ }
50
+ const extLenWords = packet.readUInt16BE(headerLength + 2);
51
+ headerLength += 4 + extLenWords * 4;
52
+ if (packet.length < headerLength) {
53
+ throw new ZaloApiError("RTP packet shorter than extension payload");
54
+ }
55
+ }
56
+ let payloadEnd = packet.length;
57
+ if (padding && packet.length > headerLength) {
58
+ const paddingLen = packet.readUInt8(packet.length - 1);
59
+ if (paddingLen > 0 && headerLength + paddingLen <= packet.length) {
60
+ payloadEnd = packet.length - paddingLen;
61
+ }
62
+ }
63
+ return {
64
+ version,
65
+ padding,
66
+ extension,
67
+ csrcCount,
68
+ marker,
69
+ payloadType,
70
+ sequenceNumber,
71
+ timestamp,
72
+ ssrc,
73
+ headerLength,
74
+ header: packet.subarray(0, headerLength),
75
+ payload: packet.subarray(headerLength, payloadEnd),
76
+ };
77
+ }
78
+ /**
79
+ * Thuật toán dẫn xuất khoá phiên SRTP/SRTCP theo RFC 3711 Mục 4.3.1 (AES-CM PRF).
80
+ */
81
+ export function deriveSrtpSessionKey(masterKey, masterSalt, label, outByteLength) {
82
+ const iv = Buffer.alloc(16, 0);
83
+ masterSalt.copy(iv, 0, 0, 14);
84
+ // label nằm tại byte thứ 7 (tức (label << 48) trong khối 112-bit salt)
85
+ iv[7] ^= label & 0xff;
86
+ const cipher = crypto.createCipheriv("aes-128-ctr", masterKey, iv);
87
+ const zeroBlock = Buffer.alloc(outByteLength, 0);
88
+ return Buffer.concat([cipher.update(zeroBlock), cipher.final()]).subarray(0, outByteLength);
89
+ }
90
+ function buildSrtpCtrIv(sessionSalt, ssrc, roc, seqNum) {
91
+ const iv = Buffer.alloc(16, 0);
92
+ sessionSalt.copy(iv, 0, 0, 14);
93
+ const ssrcBuf = Buffer.allocUnsafe(4);
94
+ ssrcBuf.writeUInt32BE(ssrc >>> 0, 0);
95
+ for (let i = 0; i < 4; i++) {
96
+ iv[4 + i] ^= ssrcBuf[i];
97
+ }
98
+ const rocBuf = Buffer.allocUnsafe(4);
99
+ rocBuf.writeUInt32BE(roc >>> 0, 0);
100
+ for (let i = 0; i < 4; i++) {
101
+ iv[8 + i] ^= rocBuf[i];
102
+ }
103
+ iv[12] ^= (seqNum >>> 8) & 0xff;
104
+ iv[13] ^= seqNum & 0xff;
105
+ return iv;
106
+ }
107
+ function buildSrtcpCtrIv(sessionSalt, ssrc, srtcpIndex) {
108
+ const iv = Buffer.alloc(16, 0);
109
+ sessionSalt.copy(iv, 0, 0, 14);
110
+ const ssrcBuf = Buffer.allocUnsafe(4);
111
+ ssrcBuf.writeUInt32BE(ssrc >>> 0, 0);
112
+ for (let i = 0; i < 4; i++) {
113
+ iv[4 + i] ^= ssrcBuf[i];
114
+ }
115
+ const idxBuf = Buffer.allocUnsafe(4);
116
+ idxBuf.writeUInt32BE((srtcpIndex & 0x7fffffff) >>> 0, 0);
117
+ for (let i = 0; i < 4; i++) {
118
+ iv[10 + i] ^= idxBuf[i];
119
+ }
120
+ return iv;
121
+ }
122
+ /**
123
+ * Phiên mã hoá / giải mã SRTP & SRTCP thuần Node.js (`node:crypto`)
124
+ * tương thích 100% với `zrtc::SrtpTransport` (`AES_CM_128_HMAC_SHA1_80` / `AES_CM_128_HMAC_SHA1_32`).
125
+ */
126
+ export class ZaloSrtpSession {
127
+ masterKey;
128
+ masterSalt;
129
+ masterKeyBase64;
130
+ suite;
131
+ tagLength;
132
+ rtpKey;
133
+ rtpAuthKey;
134
+ rtpSalt;
135
+ rtcpKey;
136
+ rtcpAuthKey;
137
+ rtcpSalt;
138
+ sendRoc = 0;
139
+ recvRoc = 0;
140
+ lastSendSeq = -1;
141
+ lastRecvSeq = -1;
142
+ srtcpIndex = 0;
143
+ constructor(masterKeyInput, suite = "AES_CM_128_HMAC_SHA1_80") {
144
+ const raw = typeof masterKeyInput === "string"
145
+ ? Buffer.from(masterKeyInput, "base64")
146
+ : Buffer.from(masterKeyInput);
147
+ if (raw.length < 30) {
148
+ throw new ZaloApiError(`SRTP master key + salt must be at least 30 bytes (got ${raw.length} bytes)`);
149
+ }
150
+ this.masterKey = raw.subarray(0, 16);
151
+ this.masterSalt = raw.subarray(16, 30);
152
+ this.masterKeyBase64 = raw.subarray(0, 30).toString("base64");
153
+ this.suite = suite;
154
+ this.tagLength = suite === "AES_CM_128_HMAC_SHA1_32" ? 4 : 10;
155
+ this.rtpKey = deriveSrtpSessionKey(this.masterKey, this.masterSalt, 0x00, 16);
156
+ this.rtpAuthKey = deriveSrtpSessionKey(this.masterKey, this.masterSalt, 0x01, 20);
157
+ this.rtpSalt = deriveSrtpSessionKey(this.masterKey, this.masterSalt, 0x02, 14);
158
+ this.rtcpKey = deriveSrtpSessionKey(this.masterKey, this.masterSalt, 0x03, 16);
159
+ this.rtcpAuthKey = deriveSrtpSessionKey(this.masterKey, this.masterSalt, 0x04, 20);
160
+ this.rtcpSalt = deriveSrtpSessionKey(this.masterKey, this.masterSalt, 0x05, 14);
161
+ }
162
+ /**
163
+ * Mã hoá gói tin RTP thành SRTP (AES-128-CTR + HMAC-SHA1 tag).
164
+ */
165
+ protectRtp(rtpPacket, explicitRoc) {
166
+ const parsed = parseRtpPacket(rtpPacket);
167
+ let roc = explicitRoc ?? this.sendRoc;
168
+ if (explicitRoc === undefined) {
169
+ if (this.lastSendSeq !== -1 && parsed.sequenceNumber < this.lastSendSeq && this.lastSendSeq - parsed.sequenceNumber > 0x8000) {
170
+ this.sendRoc = (this.sendRoc + 1) >>> 0;
171
+ roc = this.sendRoc;
172
+ }
173
+ this.lastSendSeq = parsed.sequenceNumber;
174
+ }
175
+ const iv = buildSrtpCtrIv(this.rtpSalt, parsed.ssrc, roc, parsed.sequenceNumber);
176
+ const cipher = crypto.createCipheriv("aes-128-ctr", this.rtpKey, iv);
177
+ const encryptedPayload = Buffer.concat([cipher.update(parsed.payload), cipher.final()]);
178
+ const rocBuf = Buffer.allocUnsafe(4);
179
+ rocBuf.writeUInt32BE(roc >>> 0, 0);
180
+ const hmac = crypto.createHmac("sha1", this.rtpAuthKey);
181
+ hmac.update(parsed.header);
182
+ hmac.update(encryptedPayload);
183
+ hmac.update(rocBuf);
184
+ const authTag = hmac.digest().subarray(0, this.tagLength);
185
+ return Buffer.concat([parsed.header, encryptedPayload, authTag]);
186
+ }
187
+ /**
188
+ * Xác thực HMAC-SHA1 và giải mã gói tin SRTP về RTP thuần.
189
+ */
190
+ unprotectRtp(srtpPacket, explicitRoc) {
191
+ if (!srtpPacket || srtpPacket.length < 12 + this.tagLength) {
192
+ throw new ZaloApiError(`Invalid SRTP packet length: ${srtpPacket?.length ?? 0}`);
193
+ }
194
+ const rtpPortion = srtpPacket.subarray(0, srtpPacket.length - this.tagLength);
195
+ const receivedTag = srtpPacket.subarray(srtpPacket.length - this.tagLength);
196
+ const parsed = parseRtpPacket(rtpPortion);
197
+ let roc = explicitRoc ?? this.recvRoc;
198
+ if (explicitRoc === undefined && this.lastRecvSeq !== -1) {
199
+ if (parsed.sequenceNumber < this.lastRecvSeq && this.lastRecvSeq - parsed.sequenceNumber > 0x8000) {
200
+ roc = (this.recvRoc + 1) >>> 0;
201
+ }
202
+ }
203
+ const rocBuf = Buffer.allocUnsafe(4);
204
+ rocBuf.writeUInt32BE(roc >>> 0, 0);
205
+ const hmac = crypto.createHmac("sha1", this.rtpAuthKey);
206
+ hmac.update(parsed.header);
207
+ hmac.update(parsed.payload);
208
+ hmac.update(rocBuf);
209
+ const expectedTag = hmac.digest().subarray(0, this.tagLength);
210
+ if (!crypto.timingSafeEqual(receivedTag, expectedTag)) {
211
+ throw new ZaloApiError("SRTP authentication tag verification failed");
212
+ }
213
+ if (explicitRoc === undefined) {
214
+ this.recvRoc = roc;
215
+ this.lastRecvSeq = parsed.sequenceNumber;
216
+ }
217
+ const iv = buildSrtpCtrIv(this.rtpSalt, parsed.ssrc, roc, parsed.sequenceNumber);
218
+ const decipher = crypto.createDecipheriv("aes-128-ctr", this.rtpKey, iv);
219
+ const decryptedPayload = Buffer.concat([decipher.update(parsed.payload), decipher.final()]);
220
+ return Buffer.concat([parsed.header, decryptedPayload]);
221
+ }
222
+ /**
223
+ * Mã hoá gói tin RTCP thành SRTCP (RFC 3711 Mục 3.4).
224
+ */
225
+ protectRtcp(rtcpPacket) {
226
+ if (!rtcpPacket || rtcpPacket.length < 8) {
227
+ throw new ZaloApiError(`Invalid RTCP packet length: ${rtcpPacket?.length ?? 0}`);
228
+ }
229
+ const header = rtcpPacket.subarray(0, 8);
230
+ const payload = rtcpPacket.subarray(8);
231
+ const ssrc = rtcpPacket.readUInt32BE(4);
232
+ const index = (this.srtcpIndex++ & 0x7fffffff) >>> 0;
233
+ const iv = buildSrtcpCtrIv(this.rtcpSalt, ssrc, index);
234
+ const cipher = crypto.createCipheriv("aes-128-ctr", this.rtcpKey, iv);
235
+ const encryptedPayload = Buffer.concat([cipher.update(payload), cipher.final()]);
236
+ const indexTrailer = Buffer.allocUnsafe(4);
237
+ indexTrailer.writeUInt32BE((0x80000000 | index) >>> 0, 0);
238
+ const hmac = crypto.createHmac("sha1", this.rtcpAuthKey);
239
+ hmac.update(header);
240
+ hmac.update(encryptedPayload);
241
+ hmac.update(indexTrailer);
242
+ const authTag = hmac.digest().subarray(0, this.tagLength);
243
+ return Buffer.concat([header, encryptedPayload, indexTrailer, authTag]);
244
+ }
245
+ /**
246
+ * Xác thực và giải mã gói tin SRTCP về RTCP thuần.
247
+ */
248
+ unprotectRtcp(srtcpPacket) {
249
+ if (!srtcpPacket || srtcpPacket.length < 8 + 4 + this.tagLength) {
250
+ throw new ZaloApiError(`Invalid SRTCP packet length: ${srtcpPacket?.length ?? 0}`);
251
+ }
252
+ const tagStart = srtcpPacket.length - this.tagLength;
253
+ const indexStart = tagStart - 4;
254
+ const header = srtcpPacket.subarray(0, 8);
255
+ const encryptedPayload = srtcpPacket.subarray(8, indexStart);
256
+ const indexTrailer = srtcpPacket.subarray(indexStart, tagStart);
257
+ const receivedTag = srtcpPacket.subarray(tagStart);
258
+ const hmac = crypto.createHmac("sha1", this.rtcpAuthKey);
259
+ hmac.update(header);
260
+ hmac.update(encryptedPayload);
261
+ hmac.update(indexTrailer);
262
+ const expectedTag = hmac.digest().subarray(0, this.tagLength);
263
+ if (!crypto.timingSafeEqual(receivedTag, expectedTag)) {
264
+ throw new ZaloApiError("SRTCP authentication tag verification failed");
265
+ }
266
+ const eAndIndex = indexTrailer.readUInt32BE(0);
267
+ const isEncrypted = (eAndIndex & 0x80000000) !== 0;
268
+ const index = eAndIndex & 0x7fffffff;
269
+ if (!isEncrypted) {
270
+ return Buffer.concat([header, encryptedPayload]);
271
+ }
272
+ const ssrc = header.readUInt32BE(4);
273
+ const iv = buildSrtcpCtrIv(this.rtcpSalt, ssrc, index);
274
+ const decipher = crypto.createDecipheriv("aes-128-ctr", this.rtcpKey, iv);
275
+ const decryptedPayload = Buffer.concat([decipher.update(encryptedPayload), decipher.final()]);
276
+ return Buffer.concat([header, decryptedPayload]);
277
+ }
278
+ }
@@ -0,0 +1,18 @@
1
+ import type { API } from "../apis.js";
2
+ import type { ContextSession } from "../context.js";
3
+ import { ZaloCallController, type ZaloCallControllerOptions } from "./ZaloCallController.js";
4
+ import { ZaloRtcSession, type ZaloRtcSessionOptions } from "./ZaloRtcSession.js";
5
+ export * from "./ZRTPPacketCodec.js";
6
+ export * from "./ZaloSrtpSession.js";
7
+ export * from "./ZaloRtcSession.js";
8
+ export * from "./ZaloCallTransport.js";
9
+ export * from "./ZaloCallController.js";
10
+ /**
11
+ * Factory khởi tạo phiên gọi thuần TypeScript / Node.js (`ZaloRtcSession` — UDP `ZRTPPacket` + `SRTP`)
12
+ * không phụ thuộc Zalo PC, dùng cho Worker / Server / Mobile Bridge / Desktop.
13
+ */
14
+ export declare function createRtcSessionFactory(_ctx: ContextSession, api: API): (options?: ZaloRtcSessionOptions) => ZaloRtcSession;
15
+ /**
16
+ * Factory khởi tạo bộ điều phối cuộc gọi thuần TypeScript `ZaloCallController` (`ZaloRtcSession` + `ZaloCallTransport`).
17
+ */
18
+ export declare function createCallControllerFactory(_ctx: ContextSession, api: API): (options?: ZaloCallControllerOptions) => ZaloCallController;
@@ -0,0 +1,24 @@
1
+ import { ZaloCallController } from "./ZaloCallController.js";
2
+ import { ZaloRtcSession } from "./ZaloRtcSession.js";
3
+ export * from "./ZRTPPacketCodec.js";
4
+ export * from "./ZaloSrtpSession.js";
5
+ export * from "./ZaloRtcSession.js";
6
+ export * from "./ZaloCallTransport.js";
7
+ export * from "./ZaloCallController.js";
8
+ /**
9
+ * Factory khởi tạo phiên gọi thuần TypeScript / Node.js (`ZaloRtcSession` — UDP `ZRTPPacket` + `SRTP`)
10
+ * không phụ thuộc Zalo PC, dùng cho Worker / Server / Mobile Bridge / Desktop.
11
+ */
12
+ export function createRtcSessionFactory(_ctx, api) {
13
+ return function createRtcSession(options = {}) {
14
+ return new ZaloRtcSession(api, options);
15
+ };
16
+ }
17
+ /**
18
+ * Factory khởi tạo bộ điều phối cuộc gọi thuần TypeScript `ZaloCallController` (`ZaloRtcSession` + `ZaloCallTransport`).
19
+ */
20
+ export function createCallControllerFactory(_ctx, api) {
21
+ return function createController(options = {}) {
22
+ return new ZaloCallController(api, options);
23
+ };
24
+ }
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ var ZaloApiError = require('../../Errors/ZaloApiError.cjs');
4
+ var utils = require('../../utils.cjs');
5
+ var sendVoiceCall = require('./sendVoiceCall.cjs');
6
+
7
+ const addUserGroupCallFactory = utils.apiFactory()((api, _ctx, utils) => {
8
+ const voiceCallService = api.zpwServiceMap.voice_call?.[0];
9
+ /**
10
+ * Mời thêm thành viên vào cuộc gọi nhóm đang diễn ra (`/api/voicecall/group/adduser`, command `12435`).
11
+ *
12
+ * @param callId ID của cuộc gọi nhóm
13
+ * @param options Thông tin `hostCall`, `session`, `partners`, `calleeId`, `callType` và `data`
14
+ * @throws {ZaloApiError}
15
+ */
16
+ return async function addUserGroupCall(callId, options) {
17
+ if (!callId)
18
+ throw new ZaloApiError.ZaloApiError("Missing callId");
19
+ if (!options?.hostCall)
20
+ throw new ZaloApiError.ZaloApiError("Missing hostCall");
21
+ if (!options?.session)
22
+ throw new ZaloApiError.ZaloApiError("Missing session");
23
+ if (!voiceCallService)
24
+ throw new ZaloApiError.ZaloApiError("Missing voice_call service URL in zpwServiceMap");
25
+ const dataStr = options.data === undefined
26
+ ? "{}"
27
+ : typeof options.data === "string"
28
+ ? options.data
29
+ : JSON.stringify(options.data);
30
+ const params = {
31
+ calleeId: options.calleeId ?? "",
32
+ callId,
33
+ callType: options.callType ?? sendVoiceCall.CallType.GroupAudio,
34
+ hostCall: String(options.hostCall),
35
+ data: dataStr,
36
+ session: options.session,
37
+ partners: (options.partners ?? []).map(String),
38
+ };
39
+ const encryptedParams = utils.encodeAES(JSON.stringify(params));
40
+ if (!encryptedParams)
41
+ throw new ZaloApiError.ZaloApiError("Failed to encrypt addUserGroupCall params");
42
+ const url = utils.makeURL(`${voiceCallService}/api/voicecall/group/adduser`, {
43
+ zpw_type: 24,
44
+ params: encryptedParams,
45
+ });
46
+ const response = await utils.request(url, {
47
+ method: "GET",
48
+ });
49
+ return utils.resolve(response);
50
+ };
51
+ });
52
+
53
+ exports.addUserGroupCallFactory = addUserGroupCallFactory;
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ var ZaloApiError = require('../../Errors/ZaloApiError.cjs');
4
+ var utils = require('../../utils.cjs');
5
+
6
+ const answerAckVoiceCallFactory = utils.apiFactory()((api, ctx, utils) => {
7
+ const voiceCallService = api.zpwServiceMap.voice_call?.[0];
8
+ /**
9
+ * Xác nhận bắt tay hoàn tất từ phía người gọi sau khi người nhận bắt máy (`/api/voicecall/answerack`, command `11304`, signal `408`).
10
+ *
11
+ * @param calleeId User ID của người nhận cuộc gọi
12
+ * @param callId ID của cuộc gọi
13
+ * @param options Tuỳ chọn mã trạng thái (`status`, mặc định `0`)
14
+ * @throws {ZaloApiError}
15
+ */
16
+ return async function answerAckVoiceCall(calleeId, callId, options = {}) {
17
+ if (!calleeId)
18
+ throw new ZaloApiError.ZaloApiError("Missing calleeId");
19
+ if (!callId)
20
+ throw new ZaloApiError.ZaloApiError("Missing callId");
21
+ if (!voiceCallService)
22
+ throw new ZaloApiError.ZaloApiError("Missing voice_call service URL in zpwServiceMap");
23
+ const params = {
24
+ calleeId: String(calleeId),
25
+ callId,
26
+ status: options.status ?? 0,
27
+ imei: ctx.imei,
28
+ };
29
+ const encryptedParams = utils.encodeAES(JSON.stringify(params));
30
+ if (!encryptedParams)
31
+ throw new ZaloApiError.ZaloApiError("Failed to encrypt answerAckVoiceCall params");
32
+ const url = utils.makeURL(`${voiceCallService}/api/voicecall/answerack`, {
33
+ zpw_type: 24,
34
+ params: encryptedParams,
35
+ });
36
+ const response = await utils.request(url, {
37
+ method: "GET",
38
+ });
39
+ return utils.resolve(response);
40
+ };
41
+ });
42
+
43
+ exports.answerAckVoiceCallFactory = answerAckVoiceCallFactory;
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ var ZaloApiError = require('../../Errors/ZaloApiError.cjs');
4
+ var utils = require('../../utils.cjs');
5
+ var sendVoiceCall = require('./sendVoiceCall.cjs');
6
+
7
+ const answerGroupCallFactory = utils.apiFactory()((api, ctx, utils) => {
8
+ const voiceCallService = api.zpwServiceMap.voice_call?.[0];
9
+ /**
10
+ * Chấp nhận / Nghe cuộc gọi nhóm đến (`/api/voicecall/group/answer`, command `12436`).
11
+ *
12
+ * @param callId ID của cuộc gọi nhóm
13
+ * @param options Thông tin `hostCall`, `calleeId`, `callType`, `partners` và `data`
14
+ * @throws {ZaloApiError}
15
+ */
16
+ return async function answerGroupCall(callId, options) {
17
+ if (!callId)
18
+ throw new ZaloApiError.ZaloApiError("Missing callId");
19
+ if (!options?.hostCall)
20
+ throw new ZaloApiError.ZaloApiError("Missing hostCall");
21
+ if (!voiceCallService)
22
+ throw new ZaloApiError.ZaloApiError("Missing voice_call service URL in zpwServiceMap");
23
+ const dataStr = options.data === undefined
24
+ ? "{}"
25
+ : typeof options.data === "string"
26
+ ? options.data
27
+ : JSON.stringify(options.data);
28
+ const params = {
29
+ calleeId: String(options.calleeId ?? ctx.uid),
30
+ callId,
31
+ callType: options.callType ?? sendVoiceCall.CallType.GroupAudio,
32
+ hostCall: String(options.hostCall),
33
+ data: dataStr,
34
+ partners: (options.partners ?? []).map(String),
35
+ };
36
+ const encryptedParams = utils.encodeAES(JSON.stringify(params));
37
+ if (!encryptedParams)
38
+ throw new ZaloApiError.ZaloApiError("Failed to encrypt answerGroupCall params");
39
+ const url = utils.makeURL(`${voiceCallService}/api/voicecall/group/answer`, {
40
+ zpw_type: 24,
41
+ params: encryptedParams,
42
+ });
43
+ const response = await utils.request(url, {
44
+ method: "GET",
45
+ });
46
+ return utils.resolve(response);
47
+ };
48
+ });
49
+
50
+ exports.answerGroupCallFactory = answerGroupCallFactory;
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ var ZaloApiError = require('../../Errors/ZaloApiError.cjs');
4
+ var utils = require('../../utils.cjs');
5
+
6
+ const answerVoiceCallFactory = utils.apiFactory()((api, _ctx, utils) => {
7
+ const voiceCallService = api.zpwServiceMap.voice_call?.[0];
8
+ /**
9
+ * Chấp nhận / Nghe cuộc gọi thoại hoặc video đến (`/api/voicecall/answer`, command `11303`, signal `402`).
10
+ *
11
+ * @param callerId User ID của người gọi đến
12
+ * @param callId ID của cuộc gọi
13
+ * @param options Thông tin `sessionId`, `rtpAddress`, `rtcpAddress`, `codec`, `extendData` và `status`
14
+ * @throws {ZaloApiError}
15
+ */
16
+ return async function answerVoiceCall(callerId, callId, options = {}) {
17
+ if (!callerId)
18
+ throw new ZaloApiError.ZaloApiError("Missing callerId");
19
+ if (!callId)
20
+ throw new ZaloApiError.ZaloApiError("Missing callId");
21
+ if (!voiceCallService)
22
+ throw new ZaloApiError.ZaloApiError("Missing voice_call service URL in zpwServiceMap");
23
+ const extendDataStr = options.extendData === undefined
24
+ ? undefined
25
+ : typeof options.extendData === "string"
26
+ ? options.extendData
27
+ : JSON.stringify(options.extendData);
28
+ const params = {
29
+ callerId: String(callerId),
30
+ callId,
31
+ status: options.status ?? 0,
32
+ codec: options.codec ?? "",
33
+ rtcpAddress: options.rtcpAddress ?? "",
34
+ rtpAddress: options.rtpAddress ?? "",
35
+ session_id: options.sessionId ?? "",
36
+ };
37
+ if (extendDataStr) {
38
+ params.extendData = extendDataStr;
39
+ params.subCommand = options.subCommand ?? 3;
40
+ }
41
+ const encryptedParams = utils.encodeAES(JSON.stringify(params));
42
+ if (!encryptedParams)
43
+ throw new ZaloApiError.ZaloApiError("Failed to encrypt answerVoiceCall params");
44
+ const url = utils.makeURL(`${voiceCallService}/api/voicecall/answer`, {
45
+ zpw_type: 24,
46
+ params: encryptedParams,
47
+ });
48
+ const response = await utils.request(url, {
49
+ method: "GET",
50
+ });
51
+ return utils.resolve(response);
52
+ };
53
+ });
54
+
55
+ exports.answerVoiceCallFactory = answerVoiceCallFactory;
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ var ZaloApiError = require('../../Errors/ZaloApiError.cjs');
4
+ var utils = require('../../utils.cjs');
5
+
6
+ const broadcastGroupCallStateFactory = utils.apiFactory()((api, ctx, utils) => {
7
+ const voiceCallService = api.zpwServiceMap.voice_call?.[0];
8
+ /**
9
+ * Phát (broadcast) trạng thái âm thanh / hình ảnh của thành viên trong cuộc gọi nhóm (`/api/voicecall/group/broadcast`, command `12098`).
10
+ *
11
+ * @param callId ID của cuộc gọi nhóm
12
+ * @param options Thông tin `hostCall`, `userId`, `callState`, `audioState`, `videoState` và `params`
13
+ * @throws {ZaloApiError}
14
+ */
15
+ return async function broadcastGroupCallState(callId, options) {
16
+ if (!callId)
17
+ throw new ZaloApiError.ZaloApiError("Missing callId");
18
+ if (!options?.hostCall)
19
+ throw new ZaloApiError.ZaloApiError("Missing hostCall");
20
+ if (!voiceCallService)
21
+ throw new ZaloApiError.ZaloApiError("Missing voice_call service URL in zpwServiceMap");
22
+ const paramsStr = options.params === undefined
23
+ ? "{}"
24
+ : typeof options.params === "string"
25
+ ? options.params
26
+ : JSON.stringify(options.params);
27
+ const payload = {
28
+ callId,
29
+ hostCall: String(options.hostCall),
30
+ userId: String(options.userId ?? ctx.uid),
31
+ callstate: options.callState ?? 1,
32
+ audiostate: options.audioState ?? 1,
33
+ videostate: options.videoState ?? 0,
34
+ params: paramsStr,
35
+ };
36
+ const encryptedParams = utils.encodeAES(JSON.stringify(payload));
37
+ if (!encryptedParams)
38
+ throw new ZaloApiError.ZaloApiError("Failed to encrypt broadcastGroupCallState params");
39
+ const url = utils.makeURL(`${voiceCallService}/api/voicecall/group/broadcast`, {
40
+ zpw_type: 24,
41
+ params: encryptedParams,
42
+ });
43
+ const response = await utils.request(url, {
44
+ method: "GET",
45
+ });
46
+ return utils.resolve(response);
47
+ };
48
+ });
49
+
50
+ exports.broadcastGroupCallStateFactory = broadcastGroupCallStateFactory;