@softtynet/zca-js 2.2.1 → 2.2.2

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 (94) hide show
  1. package/README.md +47 -21
  2. package/dist/apis/cancelPullMobile.d.ts +11 -0
  3. package/dist/apis/cancelPullMobile.js +33 -0
  4. package/dist/apis/cancelVoiceCall.d.ts +7 -0
  5. package/dist/apis/cancelVoiceCall.js +43 -0
  6. package/dist/apis/decryptGids.d.ts +2 -1
  7. package/dist/apis/decryptGids.js +2 -0
  8. package/dist/apis/endVoiceCall.d.ts +5 -0
  9. package/dist/apis/endVoiceCall.js +41 -0
  10. package/dist/apis/getNoisedIds.d.ts +9 -0
  11. package/dist/apis/getNoisedIds.js +67 -0
  12. package/dist/apis/getSyncData.d.ts +13 -0
  13. package/dist/apis/getSyncData.js +103 -0
  14. package/dist/apis/listen.d.ts +11 -3
  15. package/dist/apis/listen.js +71 -14
  16. package/dist/apis/preloadConversations.d.ts +20 -2
  17. package/dist/apis/preloadConversations.js +56 -3
  18. package/dist/apis/pullMobile.d.ts +15 -0
  19. package/dist/apis/pullMobile.js +38 -0
  20. package/dist/apis/requestSyncFromPhone.d.ts +17 -1
  21. package/dist/apis/requestSyncFromPhone.js +34 -20
  22. package/dist/apis/sendVoiceCall.d.ts +29 -0
  23. package/dist/apis/sendVoiceCall.js +105 -0
  24. package/dist/apis/uploadAttachment.js +6 -1
  25. package/dist/apis.d.ts +14 -0
  26. package/dist/apis.js +14 -0
  27. package/dist/cjs/apis/addReaction.cjs +2 -0
  28. package/dist/cjs/apis/addUnreadMark.cjs +2 -0
  29. package/dist/cjs/apis/cancelPullMobile.cjs +38 -0
  30. package/dist/cjs/apis/cancelVoiceCall.cjs +48 -0
  31. package/dist/cjs/apis/createReminder.cjs +2 -0
  32. package/dist/cjs/apis/decryptGids.cjs +2 -0
  33. package/dist/cjs/apis/deleteChat.cjs +2 -0
  34. package/dist/cjs/apis/deleteMessage.cjs +2 -0
  35. package/dist/cjs/apis/editReminder.cjs +2 -0
  36. package/dist/cjs/apis/endVoiceCall.cjs +46 -0
  37. package/dist/cjs/apis/findUser.cjs +2 -0
  38. package/dist/cjs/apis/findUserByUsername.cjs +2 -0
  39. package/dist/cjs/apis/forwardMessage.cjs +2 -0
  40. package/dist/cjs/apis/getAllFriends.cjs +2 -0
  41. package/dist/cjs/apis/getAvatarUrlProfile.cjs +2 -0
  42. package/dist/cjs/apis/getGroupChatHistory.cjs +2 -0
  43. package/dist/cjs/apis/getListBoard.cjs +2 -0
  44. package/dist/cjs/apis/getListReminder.cjs +2 -0
  45. package/dist/cjs/apis/getMultiUsersByPhones.cjs +2 -0
  46. package/dist/cjs/apis/getNoisedIds.cjs +72 -0
  47. package/dist/cjs/apis/getSyncData.cjs +108 -0
  48. package/dist/cjs/apis/getUserInfo.cjs +2 -0
  49. package/dist/cjs/apis/listen.cjs +71 -14
  50. package/dist/cjs/apis/preloadConversations.cjs +69 -3
  51. package/dist/cjs/apis/pullMobile.cjs +43 -0
  52. package/dist/cjs/apis/removeReminder.cjs +2 -0
  53. package/dist/cjs/apis/removeUnreadMark.cjs +2 -0
  54. package/dist/cjs/apis/requestSyncFromPhone.cjs +33 -19
  55. package/dist/cjs/apis/sendBankCard.cjs +2 -0
  56. package/dist/cjs/apis/sendCard.cjs +2 -0
  57. package/dist/cjs/apis/sendDeliveredEvent.cjs +2 -0
  58. package/dist/cjs/apis/sendLink.cjs +2 -0
  59. package/dist/cjs/apis/sendMessage.cjs +2 -0
  60. package/dist/cjs/apis/sendReport.cjs +2 -0
  61. package/dist/cjs/apis/sendSeenEvent.cjs +2 -0
  62. package/dist/cjs/apis/sendSticker.cjs +2 -0
  63. package/dist/cjs/apis/sendTypingEvent.cjs +2 -0
  64. package/dist/cjs/apis/sendVideo.cjs +2 -0
  65. package/dist/cjs/apis/sendVoice.cjs +2 -0
  66. package/dist/cjs/apis/sendVoiceCall.cjs +110 -0
  67. package/dist/cjs/apis/setHiddenConversations.cjs +2 -0
  68. package/dist/cjs/apis/setMute.cjs +2 -0
  69. package/dist/cjs/apis/setPinnedConversations.cjs +2 -0
  70. package/dist/cjs/apis/undo.cjs +2 -0
  71. package/dist/cjs/apis/updateAutoDeleteChat.cjs +2 -0
  72. package/dist/cjs/apis/uploadAttachment.cjs +8 -1
  73. package/dist/cjs/apis.cjs +14 -0
  74. package/dist/cjs/index.cjs +24 -0
  75. package/dist/cjs/models/CallEvent.cjs +51 -0
  76. package/dist/cjs/models/ClearUnread.cjs +17 -0
  77. package/dist/cjs/models/SyncEvent.cjs +54 -0
  78. package/dist/cjs/utils.cjs +153 -1
  79. package/dist/index.d.ts +11 -1
  80. package/dist/index.js +2 -0
  81. package/dist/models/CallEvent.d.ts +30 -0
  82. package/dist/models/CallEvent.js +47 -0
  83. package/dist/models/ClearUnread.d.ts +22 -0
  84. package/dist/models/ClearUnread.js +12 -0
  85. package/dist/models/SyncData.d.ts +35 -0
  86. package/dist/models/SyncData.js +1 -0
  87. package/dist/models/SyncEvent.d.ts +83 -0
  88. package/dist/models/SyncEvent.js +50 -0
  89. package/dist/models/index.d.ts +4 -0
  90. package/dist/models/index.js +4 -0
  91. package/dist/utils.d.ts +34 -0
  92. package/dist/utils.js +148 -1
  93. package/docs/REA_REVERSE_ENGINEERING_PLAN.md +140 -0
  94. package/package.json +1 -1
@@ -0,0 +1,50 @@
1
+ export var SyncEventType;
2
+ (function (SyncEventType) {
3
+ SyncEventType["USER_CONFIRM"] = "user_confirm";
4
+ SyncEventType["SYNCMSG_INFO"] = "syncmsg_info";
5
+ SyncEventType["TRANSFER_ERROR"] = "transfer_error";
6
+ SyncEventType["TRANSFER_AFTER_LOGIN"] = "transfer_after_login";
7
+ SyncEventType["UNKNOWN"] = "unknown";
8
+ })(SyncEventType || (SyncEventType = {}));
9
+ export function initializeSyncEvent(data, act, act_type) {
10
+ if (act === "user_confirm") {
11
+ return {
12
+ type: SyncEventType.USER_CONFIRM,
13
+ data: data,
14
+ act,
15
+ act_type,
16
+ };
17
+ }
18
+ else if (act === "syncmsg_info") {
19
+ return {
20
+ type: SyncEventType.SYNCMSG_INFO,
21
+ data: data,
22
+ act,
23
+ act_type,
24
+ };
25
+ }
26
+ else if (act === "transfer_error") {
27
+ return {
28
+ type: SyncEventType.TRANSFER_ERROR,
29
+ data: data,
30
+ act,
31
+ act_type,
32
+ };
33
+ }
34
+ else if (act === "transfer_after_login") {
35
+ return {
36
+ type: SyncEventType.TRANSFER_AFTER_LOGIN,
37
+ data: data,
38
+ act,
39
+ act_type,
40
+ };
41
+ }
42
+ else {
43
+ return {
44
+ type: SyncEventType.UNKNOWN,
45
+ data,
46
+ act,
47
+ act_type,
48
+ };
49
+ }
50
+ }
@@ -20,3 +20,7 @@ export * from "./User.js";
20
20
  export * from "./ZBusiness.js";
21
21
  export * from "./Label.js";
22
22
  export * from "./Sticker.js";
23
+ export * from "./ClearUnread.js";
24
+ export * from "./SyncEvent.js";
25
+ export * from "./SyncData.js";
26
+ export * from "./CallEvent.js";
@@ -20,3 +20,7 @@ export * from "./User.js";
20
20
  export * from "./ZBusiness.js";
21
21
  export * from "./Label.js";
22
22
  export * from "./Sticker.js";
23
+ export * from "./ClearUnread.js";
24
+ export * from "./SyncEvent.js";
25
+ export * from "./SyncData.js";
26
+ export * from "./CallEvent.js";
package/dist/utils.d.ts CHANGED
@@ -209,4 +209,38 @@ export declare function hexToNegativeColor(hex: string): number;
209
209
  */
210
210
  export declare function negativeColorToHex(negativeColor: number): string;
211
211
  export declare function normalizeHolderName(input?: string): string | undefined;
212
+ /**
213
+ * Generates an RSA 2048-bit key pair for mobile message synchronization (`pull_mobile_msg`)
214
+ * @returns Object containing public and private keys in both PEM and Base64 formats
215
+ */
216
+ export declare function generateRSAKeyPair(): {
217
+ publicKeyBase64: string;
218
+ privateKeyBase64: string;
219
+ publicKey: string;
220
+ privateKey: string;
221
+ };
222
+ /**
223
+ * Pure-BigInt RSA CRT PKCS#1 v1.5 decryption fallback for Node.js environments
224
+ * where OpenSSL blocks `RSA_PKCS1_PADDING` in `crypto.privateDecrypt` (CVE-2023-46809).
225
+ */
226
+ export declare function rsaDecryptPkcs1Manual(privateKeyPem: string, cipher: Buffer): Buffer;
227
+ /**
228
+ * Decrypt `encrypted_key` from `syncmsg_info` using RSA private key with PKCS#1 v1.5 scheme.
229
+ * Automatically falls back to pure-BigInt CRT decryption if OpenSSL disables `RSA_PKCS1_PADDING`.
230
+ *
231
+ * @param privateKey - RSA private key in PEM format
232
+ * @param encryptedBase64 - Base64 encoded `encrypted_key` from `syncmsg_info`
233
+ * @returns Raw decrypted bytes (typically 16-32 bytes; use `deriveZdbAesKey` for ZDB4.0 AES-256-CBC key)
234
+ */
235
+ export declare function decryptWithPrivateKey(privateKey: string, encryptedBase64: string): Buffer;
236
+ /**
237
+ * Derives the 32-byte AES-256-CBC key for Zalo ZDB4.0 (`.db.crypt`) backup files
238
+ * from the raw RSA-decrypted bytes: first 32 uppercase hex characters as ASCII bytes.
239
+ */
240
+ export declare function deriveZdbAesKey(decryptedRsaBytes: Buffer): Buffer;
241
+ /**
242
+ * Decrypts a Zalo ZDB4.0 (`.db.crypt`) backup buffer into its plaintext ZDB4.0 container
243
+ * (header + table of contents + XZ stream) using 64KB independent AES-256-CBC chunks (`IV = 0`).
244
+ */
245
+ export declare function decryptZdbBuffer(zdbBuffer: Buffer, aesKey: Buffer): Buffer;
212
246
  export {};
package/dist/utils.js CHANGED
@@ -679,7 +679,16 @@ export async function handleZaloResponse(ctx, response, isEncrypted = true) {
679
679
  };
680
680
  return result;
681
681
  }
682
- const decodedData = isEncrypted ? JSON.parse(decodeAES(ctx.secretKey, jsonData.data)) : jsonData;
682
+ if (isEncrypted && (!jsonData.data || jsonData.data.length === 0)) {
683
+ result.data = {};
684
+ return result;
685
+ }
686
+ const decodedStr = isEncrypted ? decodeAES(ctx.secretKey, jsonData.data) : null;
687
+ if (isEncrypted && (!decodedStr || decodedStr.length === 0)) {
688
+ result.data = {};
689
+ return result;
690
+ }
691
+ const decodedData = isEncrypted ? JSON.parse(decodedStr) : jsonData;
683
692
  if (decodedData.error_code != 0) {
684
693
  result.error = {
685
694
  message: decodedData.error_message,
@@ -804,3 +813,141 @@ export function normalizeHolderName(input) {
804
813
  .trim();
805
814
  return normalized.length >= 5 ? normalized : undefined;
806
815
  }
816
+ /**
817
+ * Generates an RSA 2048-bit key pair for mobile message synchronization (`pull_mobile_msg`)
818
+ * @returns Object containing public and private keys in both PEM and Base64 formats
819
+ */
820
+ export function generateRSAKeyPair() {
821
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
822
+ modulusLength: 2048,
823
+ publicKeyEncoding: { type: "spki", format: "pem" },
824
+ privateKeyEncoding: { type: "pkcs8", format: "pem" },
825
+ });
826
+ const publicKeyBase64 = publicKey
827
+ .toString()
828
+ .replace(/-----BEGIN PUBLIC KEY-----/g, "")
829
+ .replace(/-----END PUBLIC KEY-----/g, "")
830
+ .replace(/\s+/g, "");
831
+ const privateKeyBase64 = privateKey
832
+ .toString()
833
+ .replace(/-----BEGIN PRIVATE KEY-----/g, "")
834
+ .replace(/-----END PRIVATE KEY-----/g, "")
835
+ .replace(/\s+/g, "");
836
+ return { publicKeyBase64, privateKeyBase64, publicKey: publicKey.toString(), privateKey: privateKey.toString() };
837
+ }
838
+ function b64urlToBigInt(value) {
839
+ return BigInt("0x" + Buffer.from(value, "base64url").toString("hex"));
840
+ }
841
+ function modPow(base, exp, mod) {
842
+ const zero = BigInt(0);
843
+ const one = BigInt(1);
844
+ let result = one;
845
+ base %= mod;
846
+ while (exp > zero) {
847
+ if (exp & one)
848
+ result = (result * base) % mod;
849
+ base = (base * base) % mod;
850
+ exp >>= one;
851
+ }
852
+ return result;
853
+ }
854
+ /**
855
+ * Pure-BigInt RSA CRT PKCS#1 v1.5 decryption fallback for Node.js environments
856
+ * where OpenSSL blocks `RSA_PKCS1_PADDING` in `crypto.privateDecrypt` (CVE-2023-46809).
857
+ */
858
+ export function rsaDecryptPkcs1Manual(privateKeyPem, cipher) {
859
+ const jwk = crypto.createPrivateKey(privateKeyPem).export({ format: "jwk" });
860
+ const n = b64urlToBigInt(jwk.n);
861
+ const p = b64urlToBigInt(jwk.p);
862
+ const q = b64urlToBigInt(jwk.q);
863
+ const dp = b64urlToBigInt(jwk.dp);
864
+ const dq = b64urlToBigInt(jwk.dq);
865
+ const qi = b64urlToBigInt(jwk.qi);
866
+ const c = BigInt("0x" + cipher.toString("hex"));
867
+ const m1 = modPow(c, dp, p);
868
+ const m2 = modPow(c, dq, q);
869
+ const h = (qi * (((m1 - m2) % p) + p)) % p;
870
+ const m = m2 + h * q;
871
+ const k = Math.ceil(n.toString(16).length / 2);
872
+ const em = Buffer.from(m.toString(16).padStart(k * 2, "0"), "hex");
873
+ if (em[0] !== 0x00 || em[1] !== 0x02)
874
+ throw new ZaloApiError("Invalid RSA PKCS#1 v1.5 padding");
875
+ const sep = em.indexOf(0x00, 2);
876
+ if (sep < 10)
877
+ throw new ZaloApiError("Invalid RSA PKCS#1 v1.5 padding separator");
878
+ return em.subarray(sep + 1);
879
+ }
880
+ /**
881
+ * Decrypt `encrypted_key` from `syncmsg_info` using RSA private key with PKCS#1 v1.5 scheme.
882
+ * Automatically falls back to pure-BigInt CRT decryption if OpenSSL disables `RSA_PKCS1_PADDING`.
883
+ *
884
+ * @param privateKey - RSA private key in PEM format
885
+ * @param encryptedBase64 - Base64 encoded `encrypted_key` from `syncmsg_info`
886
+ * @returns Raw decrypted bytes (typically 16-32 bytes; use `deriveZdbAesKey` for ZDB4.0 AES-256-CBC key)
887
+ */
888
+ export function decryptWithPrivateKey(privateKey, encryptedBase64) {
889
+ const cleanKey = privateKey.replace(/\\n/g, "\n");
890
+ const encryptedBuffer = Buffer.from(encryptedBase64, "base64");
891
+ try {
892
+ return crypto.privateDecrypt({
893
+ key: cleanKey,
894
+ padding: crypto.constants.RSA_PKCS1_PADDING,
895
+ }, encryptedBuffer);
896
+ }
897
+ catch (err) {
898
+ const errorObj = err;
899
+ const code = String((errorObj === null || errorObj === void 0 ? void 0 : errorObj.code) || "");
900
+ const msg = String((errorObj === null || errorObj === void 0 ? void 0 : errorObj.message) || "");
901
+ if (code.includes("INVALID_ARG") || /RSA_PKCS1_PADDING|security-revert|not supported/i.test(msg)) {
902
+ return rsaDecryptPkcs1Manual(cleanKey, encryptedBuffer);
903
+ }
904
+ throw err;
905
+ }
906
+ }
907
+ /**
908
+ * Derives the 32-byte AES-256-CBC key for Zalo ZDB4.0 (`.db.crypt`) backup files
909
+ * from the raw RSA-decrypted bytes: first 32 uppercase hex characters as ASCII bytes.
910
+ */
911
+ export function deriveZdbAesKey(decryptedRsaBytes) {
912
+ const upperHex = decryptedRsaBytes.toString("hex").toUpperCase();
913
+ if (upperHex.length < 32) {
914
+ throw new ZaloApiError(`Decrypted RSA key hex is too short (${upperHex.length} chars, expected >= 32)`);
915
+ }
916
+ return Buffer.from(upperHex.slice(0, 32), "ascii");
917
+ }
918
+ /**
919
+ * Decrypts a Zalo ZDB4.0 (`.db.crypt`) backup buffer into its plaintext ZDB4.0 container
920
+ * (header + table of contents + XZ stream) using 64KB independent AES-256-CBC chunks (`IV = 0`).
921
+ */
922
+ export function decryptZdbBuffer(zdbBuffer, aesKey) {
923
+ const ZDB_MAGIC = Buffer.from("ZDB4.0", "ascii");
924
+ const CHUNK_SIZE = 0x10000;
925
+ const zeroIv = Buffer.alloc(16, 0);
926
+ for (const startOffset of [0, 4]) {
927
+ const encryptedLength = Math.floor((zdbBuffer.length - startOffset) / 16) * 16;
928
+ if (encryptedLength < 16)
929
+ continue;
930
+ const firstChunkLen = Math.min(CHUNK_SIZE, encryptedLength);
931
+ const firstDecipher = crypto.createDecipheriv("aes-256-cbc", aesKey, zeroIv);
932
+ firstDecipher.setAutoPadding(false);
933
+ const firstPlain = Buffer.concat([
934
+ firstDecipher.update(zdbBuffer.subarray(startOffset, startOffset + firstChunkLen)),
935
+ firstDecipher.final(),
936
+ ]);
937
+ if (!firstPlain.subarray(0, ZDB_MAGIC.length).equals(ZDB_MAGIC)) {
938
+ continue;
939
+ }
940
+ const chunks = [firstPlain];
941
+ for (let offset = CHUNK_SIZE; offset < encryptedLength; offset += CHUNK_SIZE) {
942
+ const len = Math.min(CHUNK_SIZE, encryptedLength - offset);
943
+ const decipher = crypto.createDecipheriv("aes-256-cbc", aesKey, zeroIv);
944
+ decipher.setAutoPadding(false);
945
+ chunks.push(Buffer.concat([
946
+ decipher.update(zdbBuffer.subarray(startOffset + offset, startOffset + offset + len)),
947
+ decipher.final(),
948
+ ]));
949
+ }
950
+ return Buffer.concat(chunks);
951
+ }
952
+ throw new ZaloApiError("Invalid AES key or buffer is not in ZDB4.0 format");
953
+ }
@@ -0,0 +1,140 @@
1
+ # Tài Liệu & Kế Hoạch Sử Dụng REA (`morluto/rea`) Cho Dự Án `zca-js`
2
+
3
+ Tài liệu này là sổ tay kỹ thuật hoàn chỉnh hướng dẫn cài đặt, vận hành bộ công cụ **REA (`rea-agents`)** ([github.com/morluto/rea](https://github.com/morluto/rea) | [rea.tools](https://rea.tools/)) và **Kế hoạch 4 giai đoạn** áp dụng REA để dịch ngược các tính năng nâng cao của Zalo Desktop (`app.asar` + Native Addons) và Zalo Web (`chat.zalo.me`).
4
+
5
+ ---
6
+
7
+ ## 1. Tổng Quan Về REA (`rea-agents`)
8
+
9
+ **REA (Reverse Engineering Assistant)** là một bộ công cụ **CLI + MCP Server** chuyên dụng giúp kết nối các AI Coding Agents (như Antigravity, Claude Code, Cursor, Codex) trực tiếp với các trình dịch ngược phần mềm:
10
+
11
+ | Thành phần mục tiêu | Công cụ REA sử dụng | Ứng dụng trong `zca-js` |
12
+ | :--- | :--- | :--- |
13
+ | **Gói Electron (`app.asar`)** | `analyze-javascript-application` | Giải nén, quét AST, trích xuất bảng `commandId`, endpoint REST/WebSocket, hằng số mã hóa từ `/Applications/Zalo.app/Contents/Resources/app.asar`. |
14
+ | **Web App (`chat.zalo.me`)** | `analyze-website` + HAR Capture | Quét các webpack chunks, WebWorker, WASM (`libzproto_wasm_bg.wasm`) và phân tích lưu lượng mạng thực tế (`.har`). |
15
+ | **Cơ sở dữ liệu cục bộ** | SQLite Inspector | Kiểm tra cấu trúc bảng `ChatContent`, `message`, `conversation` trong file sao lưu `ZDB4.0` (`.db.crypt`) của Zalo Mobile/PC. |
16
+ | **Native Binary (`.node` / `.dylib`)** | Ghidra / Hopper / IDA Bridge | Dịch ngược mã máy C/C++ trong `zcall.node`, `libzrtc.dylib`, `zcrypto.node` để lấy thuật toán mã hóa media (ZRTP) và chữ ký native. |
17
+
18
+ ---
19
+
20
+ ## 2. Cài Đặt & Cấu Hình Chuẩn Bị
21
+
22
+ ### 2.1. Yêu cầu hệ thống
23
+ 1. **Node.js `>= 22.19.0` hoặc `24.x`** (bắt buộc vì `rea-agents` sử dụng module native `node:sqlite` để lập chỉ mục các hàm/symbol khi dịch ngược):
24
+ ```bash
25
+ # Kiểm tra phiên bản Node hiện tại
26
+ node -v
27
+
28
+ # Nếu đang ở Node 20, chuyển sang Node 22+ qua nvm hoặc fnm:
29
+ nvm install 22 && nvm use 22
30
+ ```
31
+ 2. **Ứng dụng Zalo Desktop trên macOS**:
32
+ - Đường dẫn ASAR: `/Applications/Zalo.app/Contents/Resources/app.asar`
33
+ - Đường dẫn Native Addons đã giải nén: `/Applications/Zalo.app/Contents/Resources/app.asar.unpacked/`
34
+ 3. **Trình dịch ngược Binary (tuỳ chọn cho Giai đoạn 1 & 3)**:
35
+ - **Ghidra** (miễn phí, mã nguồn mở) hoặc **Hopper Disassembler** / **IDA Pro** trên macOS.
36
+
37
+ ### 2.2. Cài đặt và kết nối MCP Server
38
+ Chạy lệnh khởi tạo tự động của REA để đăng ký MCP server vào trình soạn thảo/AI Agent:
39
+ ```bash
40
+ npx -y rea-agents@latest setup
41
+ ```
42
+ Hoặc cấu hình thủ công trong `mcp_config.json`:
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "rea": {
47
+ "command": "npx",
48
+ "args": ["-y", "rea-agents@latest", "mcp"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ---
55
+
56
+ ## 3. Sổ Tay Các Lệnh REA Thực Chiến Với Zalo
57
+
58
+ ### 3.1. Phân tích gói Electron `app.asar` của Zalo PC
59
+ ```bash
60
+ npx -y rea-agents@latest analyze-javascript-application \
61
+ "/Applications/Zalo.app/Contents/Resources/app.asar" \
62
+ --json
63
+ ```
64
+ - Lệnh này giúp lập chỉ mục toàn bộ các webpack modules bên trong `app.asar` (ví dụ module chứa bảng định tuyến `commandId` từ `10001` đến `16228`, `SyncMessageController`, `BackupMsgController`, `VoiceCallDomain`, v.v.).
65
+
66
+ ### 3.2. Phân tích trực tiếp `chat.zalo.me`
67
+ ```bash
68
+ npx -y rea-agents@latest analyze-website https://chat.zalo.me --json
69
+ ```
70
+
71
+ ### 3.3. Giải nén `app.asar` ra thư mục tạm để tra cứu sâu kết hợp REA
72
+ Khi cần kết hợp REA với tìm kiếm mã nguồn trực tiếp:
73
+ ```bash
74
+ npx -y @electron/asar extract \
75
+ "/Applications/Zalo.app/Contents/Resources/app.asar" \
76
+ "/tmp/zalo-asar-extracted"
77
+ ```
78
+
79
+ ---
80
+
81
+ ## 4. Hiện Trạng Các Tính Năng Đã Hoàn Thiện (100% Verified) Trong `zca-js`
82
+
83
+ Trước khi dùng REA cho các tính năng mới, dưới đây là các tính năng **đã được xác minh 100% từ `app.asar` và tích hợp sẵn vào nhánh `lib` của `zca-js`**:
84
+
85
+ | Nhóm tính năng | API / Sự kiện trong `zca-js` | Command ID / Giao thức Zalo | Trạng thái |
86
+ | :--- | :--- | :--- | :--- |
87
+ | **Lấy bù tin nhắn khi mở lại app (Offline Catch-up)** | `api.preloadConversations(threadIdLocalMsgId)` | `10053` (`/api/preloadconvers/get-last-msgs`) | ✅ Hoàn tất 100% |
88
+ | **Kéo hàng đợi tin nhắn nhỡ qua WebSocket** | `api.listener.requestOldMessages(threadType, lastMsgId)` + sự kiện `old_messages` | WS `cmd: 510` (User) & `cmd: 511` (Group), `subCmd: 0 \| 1` | ✅ Hoàn tất 100% |
89
+ | **Đồng bộ trạng thái đã đọc liên thiết bị** | Sự kiện `api.listener.on("unread_cleared", ...)` | WS `cmd: 504` / `524` (`clearUnreads`) | ✅ Hoàn tất 100% |
90
+ | **Kích hoạt đồng bộ từ điện thoại (Mobile Backup Sync)** | `api.requestSyncFromPhone()`, `api.pullMobile()`, `api.cancelPullMobile()` | `12000` (`pull_mobile_msg`), `12700` (`cancel_pull_mobile_msg`) | ✅ Hoàn tất 100% |
91
+ | **Lắng nghe tiến trình đồng bộ từ điện thoại** | Sự kiện `api.listener.on("sync_event", ...)` (`USER_CONFIRM`, `SYNCMSG_INFO`, `TRANSFER_ERROR`, `TRANSFER_AFTER_LOGIN`) | WS `cmd: 601`, `act_type: "syncmsgmb"` & `"transfer_msg"` | ✅ Hoàn tất 100% |
92
+ | **Tải & giải mã gói sao lưu `ZDB4.0` (`.db.crypt`)** | `api.getSyncData()`, `decryptWithPrivateKey()`, `deriveZdbAesKey()`, `decryptZdbBuffer()` | RSA-2048 PKCS#1 v1.5 + AES-256-CBC 64KB chunk (`ZDB4.0`) | ✅ Hoàn tất 100% |
93
+ | **Quy đổi UID/GID số thuần sang Noised ID 19 số** | `api.getNoisedIds({ fids, gids })` & `api.decryptGids(gids)` | `12390` (`/api/znoise`) & `12054` (`/api/gid/decrypt`) | ✅ Hoàn tất 100% |
94
+ | **Báo hiệu Gọi Điện Thoại (Voice/Video Call Signaling)** | `api.sendVoiceCall()`, `api.cancelVoiceCall()`, `api.endVoiceCall()` + sự kiện `call_event` | `11300` (`requestcall`), `11301` (`request`), `11305` (`cancel`), `11306` (`endcall`), WS `act_type: "voip"` | ✅ Hoàn tất 100% |
95
+
96
+ ---
97
+
98
+ ## 5. Kế Hoạch 4 Giai Đoạn Sử Dụng REA Cho Các Tính Năng Chưa Có
99
+
100
+ Dưới đây là lộ trình chi tiết sử dụng **REA** để nghiên cứu và mở khóa 4 nhóm tính năng phức tạp tiếp theo của Zalo:
101
+
102
+ ### Giai đoạn 1: Giải mã Media Plane 2 chiều (Đàm thoại âm thanh RTP/ZRTP thực sự)
103
+ - **Vấn đề hiện tại**: `api.sendVoiceCall()` đã làm đổ chuông điện thoại người nhận và lấy về thành công `{ sessId, rtpServer, rtcpServer, zrtpKey, codec }`, đồng thời nhận được sự kiện `call_event` khi người nhận bắt máy (`CallEventType.ANSWER`). Tuy nhiên, để truyền/nhận giọng nói 2 chiều sau khi bắt máy, Zalo PC không dùng WebRTC của trình duyệt mà uỷ quyền cho native addon `zcall.node` + `libzrtc.dylib`.
104
+ - **Mục tiêu dùng REA**:
105
+ 1. Dùng REA kết nối với **Ghidra/Hopper** mở 2 file native trong `/Applications/Zalo.app/Contents/Resources/app.asar.unpacked/`:
106
+ - `zcall.node` (Bridge IPC giữa Electron JS và C++)
107
+ - `libzrtc.dylib` (Engine xử lý giao thức UDP RTP/RTCP + mã hóa ZRTP)
108
+ 2. Tìm các hàm xuất (exports) liên quan đến `initCall`, `sendDataToNative`, `recvSignal` (đặc biệt khi nhận `command: 401`, `416`, `402` trong `app.asar`) và `InitZRTP`.
109
+ 3. Xác định định dạng header gói tin UDP đầu tiên gửi tới `rtpServer` / `rtcpServer` (cách đóng gói `sessId`, `callId`, `zrtpKey` và codec Opus payload type).
110
+ 4. Viết module `VoiceCallMediaSession` bằng `node:dgram` (UDP socket) trong `zca-js` để cho phép phát file âm thanh (`.wav`/`.opus`) vào cuộc gọi hoặc thu âm luồng thoại của đối phương.
111
+
112
+ ### Giai đoạn 2: Giải mã giao thức Đồng bộ thế hệ 2 (`Transfer-Sync-V2` & WebAssembly)
113
+ - **Vấn đề hiện tại**: Ngoài luồng `pull_mobile_msg` (`12000` - `ZDB4.0`), các bản Zalo mới đang triển khai song song giao thức `/api/transfer-sync-v2/request-sync` (command `12888`) và sự kiện `act_type: "transfer_sync2"` sử dụng file WebAssembly `libzproto_wasm_bg.wasm`.
114
+ - **Mục tiêu dùng REA**:
115
+ 1. Dùng REA phân tích class `SyncController` và `_processSync2Ctrl` trong `app.asar` để lấy toàn bộ state machine của `transfer_sync2`.
116
+ 2. Dùng REA + Ghidra (với plugin WASM) hoặc `wasm2wat` phân tích file `libzproto_wasm_bg.wasm` để trích xuất schema Protobuf của tham số `data` trong:
117
+ - `/api/transfer-sync-v2/request-sync` (`12888`)
118
+ - `/api/transfer-sync-v2/download`
119
+ 3. Tích hợp bộ parser Protobuf thuần TypeScript vào `zca-js` mà không cần phụ thuộc binary WASM nặng nề.
120
+
121
+ ### Giai đoạn 3: Giải mã Tin nhắn Mã hóa Đầu cuối (`CHAT_E2EE`, commands `16000`–`16201`)
122
+ - **Vấn đề hiện tại**: Khi một cuộc trò chuyện bật "Mã hóa đầu cuối (E2EE)", tin nhắn trả về nằm trong mảng `eeMsgs` (khi gọi `preloadConversations`) và sử dụng cụm endpoint `/api/e2ee/pc/message/*` (`16000`–`16201`).
123
+ - **Mục tiêu dùng REA**:
124
+ 1. Dùng REA quét toàn bộ các tham chiếu tới `16000`–`16201` (`getE2eeDomain()`, `deleteOneOneE2eeMessageV2`, `EntriesTransferManager`) trong `app.asar`.
125
+ 2. Dùng REA kết nối Ghidra phân tích `zcrypto.node` để xác định cơ chế trao đổi khoá (Signal Protocol / Double Ratchet hoặc X25519 + AES-GCM của Zalo).
126
+ 3. Bổ sung `sendE2EEMessage` và bộ giải mã tự động cho `eeMsgs` trong `listen.ts` và `preloadConversations.ts`.
127
+
128
+ ### Giai đoạn 4: Gọi Nhóm (`Group Voice/Video Call`, commands `12433`–`12447`)
129
+ - **Vấn đề hiện tại**: Từ `app.asar`, chúng ta đã trích xuất được danh sách đầy đủ các API gọi nhóm:
130
+ - `12433`: `/api/voicecall/group/requestcall` (`{ groupId, callId, typeRequest, data, partners }`)
131
+ - `12434`: `/api/voicecall/group/request` (`{ calleeId, callId, callType, data, session, partners, groupId }`)
132
+ - `12435`: `/api/voicecall/group/adduser`
133
+ - `12436`: `/api/voicecall/group/answer`
134
+ - `12437`: `/api/voicecall/group/cancel`
135
+ - `12438`: `/api/voicecall/group/endcall`
136
+ - `12439`: `/api/voicecall/group/ringring`
137
+ - `12445`: `/api/voicecall/group/state`
138
+ - **Mục tiêu dùng REA**:
139
+ 1. Dùng tính năng phân tích HAR của REA khi thực hiện 1 cuộc gọi nhóm thực tế trên Zalo PC/Web để bắt chính xác cấu trúc chuỗi JSON bên trong trường `data` và mảng `partners`.
140
+ 2. Sau khi có mẫu payload thực tế từ HAR, đóng gói thành các API `sendGroupVoiceCall`, `cancelGroupVoiceCall`, `endGroupVoiceCall` trong `zca-js`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softtynet/zca-js",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "description": "Unofficial Zalo API for JavaScript",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",