@softtynet/zca-js 2.3.2 → 2.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +63 -21
  2. package/dist/apis/auth/exportSession.d.ts +7 -0
  3. package/dist/apis/auth/exportSession.js +17 -0
  4. package/dist/apis/chat/getDirectMessageStatus.d.ts +14 -0
  5. package/dist/apis/chat/getDirectMessageStatus.js +52 -0
  6. package/dist/apis/chat/getSuggestedConversations.d.ts +5 -0
  7. package/dist/apis/chat/getSuggestedConversations.js +24 -0
  8. package/dist/apis/chat/updatePinConversations.d.ts +24 -0
  9. package/dist/apis/chat/updatePinConversations.js +40 -0
  10. package/dist/apis/friend/getFriendProfilesV2.d.ts +26 -0
  11. package/dist/apis/friend/getFriendProfilesV2.js +38 -0
  12. package/dist/apis/friend/getFriendsActivePCWeb.d.ts +6 -0
  13. package/dist/apis/friend/getFriendsActivePCWeb.js +15 -0
  14. package/dist/apis/friend/verifyFriend.d.ts +11 -0
  15. package/dist/apis/friend/verifyFriend.js +37 -0
  16. package/dist/apis/group/analyseGroup.d.ts +4 -0
  17. package/dist/apis/group/analyseGroup.js +28 -0
  18. package/dist/apis/group/checkQuotaCommunityMember.d.ts +6 -0
  19. package/dist/apis/group/checkQuotaCommunityMember.js +34 -0
  20. package/dist/apis/group/getE2eeGroupList.d.ts +7 -0
  21. package/dist/apis/group/getE2eeGroupList.js +15 -0
  22. package/dist/apis/group/getGroupChatHistory.d.ts +37 -6
  23. package/dist/apis/group/getGroupChatHistory.js +41 -19
  24. package/dist/apis/group/getGroupCloudMessagePath.d.ts +19 -0
  25. package/dist/apis/group/getGroupCloudMessagePath.js +51 -0
  26. package/dist/apis/group/getGroupHistoryLegacy.d.ts +7 -0
  27. package/dist/apis/group/getGroupHistoryLegacy.js +62 -0
  28. package/dist/apis/group/getGroupInfoV2.d.ts +30 -0
  29. package/dist/apis/group/getGroupInfoV2.js +48 -0
  30. package/dist/apis/group/getGroupListV3.d.ts +19 -0
  31. package/dist/apis/group/getGroupListV3.js +29 -0
  32. package/dist/apis/group/getGroupMessageStatus.d.ts +26 -0
  33. package/dist/apis/group/getGroupMessageStatus.js +77 -0
  34. package/dist/apis/group/getMultiGroupCloudMessages.d.ts +5 -0
  35. package/dist/apis/group/getMultiGroupCloudMessages.js +29 -0
  36. package/dist/apis/media/getConversationMedia.d.ts +10 -0
  37. package/dist/apis/media/getPersonalMedia.d.ts +8 -0
  38. package/dist/apis/media/getPersonalMedia.js +31 -0
  39. package/dist/apis/media/getPersonalMediaPreview.d.ts +3 -0
  40. package/dist/apis/media/getPersonalMediaPreview.js +24 -0
  41. package/dist/apis/media/removePersonalMedia.d.ts +9 -0
  42. package/dist/apis/media/removePersonalMedia.js +28 -0
  43. package/dist/apis.d.ts +57 -0
  44. package/dist/apis.js +95 -0
  45. package/dist/cjs/apis/auth/exportSession.cjs +22 -0
  46. package/dist/cjs/apis/chat/getDirectMessageStatus.cjs +58 -0
  47. package/dist/cjs/apis/chat/getSuggestedConversations.cjs +29 -0
  48. package/dist/cjs/apis/chat/updatePinConversations.cjs +45 -0
  49. package/dist/cjs/apis/friend/getFriendProfilesV2.cjs +43 -0
  50. package/dist/cjs/apis/friend/getFriendsActivePCWeb.cjs +20 -0
  51. package/dist/cjs/apis/friend/verifyFriend.cjs +42 -0
  52. package/dist/cjs/apis/group/analyseGroup.cjs +33 -0
  53. package/dist/cjs/apis/group/checkQuotaCommunityMember.cjs +39 -0
  54. package/dist/cjs/apis/group/getE2eeGroupList.cjs +20 -0
  55. package/dist/cjs/apis/group/getGroupChatHistory.cjs +41 -18
  56. package/dist/cjs/apis/group/getGroupCloudMessagePath.cjs +69 -0
  57. package/dist/cjs/apis/group/getGroupHistoryLegacy.cjs +80 -0
  58. package/dist/cjs/apis/group/getGroupInfoV2.cjs +53 -0
  59. package/dist/cjs/apis/group/getGroupListV3.cjs +34 -0
  60. package/dist/cjs/apis/group/getGroupMessageStatus.cjs +83 -0
  61. package/dist/cjs/apis/group/getMultiGroupCloudMessages.cjs +34 -0
  62. package/dist/cjs/apis/media/getPersonalMedia.cjs +36 -0
  63. package/dist/cjs/apis/media/getPersonalMediaPreview.cjs +29 -0
  64. package/dist/cjs/apis/media/removePersonalMedia.cjs +33 -0
  65. package/dist/cjs/apis.cjs +95 -0
  66. package/dist/cjs/constants/endpoints.cjs +13 -0
  67. package/dist/cjs/constants/protocol.cjs +1 -1
  68. package/dist/cjs/context.cjs +20 -2
  69. package/dist/cjs/core/zcloud-client.cjs +1 -2
  70. package/dist/cjs/crypto/ws-cipher.cjs +66 -2
  71. package/dist/cjs/index.cjs +2 -0
  72. package/dist/cjs/realtime/listener.cjs +18 -6
  73. package/dist/cjs/update.cjs +10 -1
  74. package/dist/cjs/utils.cjs +22 -5
  75. package/dist/constants/endpoints.d.ts +13 -0
  76. package/dist/constants/endpoints.js +13 -0
  77. package/dist/constants/protocol.d.ts +1 -1
  78. package/dist/constants/protocol.js +1 -1
  79. package/dist/context.d.ts +10 -0
  80. package/dist/context.js +20 -2
  81. package/dist/crypto/ws-cipher.d.ts +7 -0
  82. package/dist/crypto/ws-cipher.js +64 -2
  83. package/dist/index.d.ts +22 -3
  84. package/dist/index.js +1 -1
  85. package/dist/realtime/listener.d.ts +1 -0
  86. package/dist/realtime/listener.js +18 -6
  87. package/dist/update.js +10 -1
  88. package/dist/utils.js +17 -4
  89. package/index.d.ts +1 -1
  90. package/package.json +15 -5
@@ -15,8 +15,7 @@ var protocol = require('./constants/protocol.cjs');
15
15
  var domains = require('./constants/domains.cjs');
16
16
  var defaults = require('./constants/defaults.cjs');
17
17
  var aes = require('./crypto/aes.cjs');
18
- require('pako');
19
- require('json-bigint');
18
+ var wsCipher = require('./crypto/ws-cipher.cjs');
20
19
 
21
20
  const isBun = typeof Bun !== "undefined";
22
21
  function hasOwn(obj, key) {
@@ -166,8 +165,20 @@ async function request(ctx, url, options, raw = false) {
166
165
  options = { ...(options || {}), headers: defaultHeaders };
167
166
  }
168
167
  }
168
+ const timeoutMs = ctx.options?.requestTimeout;
169
+ const defaultSignal = !options?.signal &&
170
+ typeof timeoutMs === "number" &&
171
+ timeoutMs > 0 &&
172
+ typeof AbortSignal !== "undefined" &&
173
+ typeof AbortSignal.timeout === "function"
174
+ ? AbortSignal.timeout(timeoutMs)
175
+ : undefined;
176
+ const isAgentUndiciDispatcher = Boolean(ctx.options.agent && typeof ctx.options.agent.dispatch === "function");
177
+ const resolvedDispatcher = ctx.options.dispatcher ?? (isAgentUndiciDispatcher ? ctx.options.agent : undefined);
178
+ const resolvedAgent = ctx.options.agent && !isAgentUndiciDispatcher ? ctx.options.agent : undefined;
169
179
  const _options = {
170
180
  ...(options ?? {}),
181
+ ...(defaultSignal ? { signal: defaultSignal } : {}),
171
182
  ...(isBun
172
183
  ? {
173
184
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -175,8 +186,8 @@ async function request(ctx, url, options, raw = false) {
175
186
  proxy: ctx.options.agent?.proxy?.href,
176
187
  }
177
188
  : {
178
- agent: ctx.options.agent,
179
- dispatcher: ctx.options.agent,
189
+ ...(resolvedAgent ? { agent: resolvedAgent } : {}),
190
+ ...(resolvedDispatcher ? { dispatcher: resolvedDispatcher } : {}),
180
191
  }),
181
192
  };
182
193
  const response = await ctx.options.polyfill(url, _options);
@@ -481,7 +492,8 @@ async function handleZaloResponse(ctx, response, isEncrypted = true) {
481
492
  result.data = {};
482
493
  return result;
483
494
  }
484
- const decodedData = isEncrypted ? JSON.parse(decodedStr) : jsonData;
495
+ const safeJsonStr = isEncrypted ? wsCipher.preParseJsonIds(decodedStr) : null;
496
+ const decodedData = isEncrypted ? JSON.parse(safeJsonStr) : jsonData;
485
497
  if (decodedData.error_code != 0) {
486
498
  result.error = {
487
499
  message: decodedData.error_message,
@@ -554,6 +566,11 @@ exports.decryptResp = aes.decryptResp;
554
566
  exports.encodeAES = aes.encodeAES;
555
567
  exports.encryptPin = aes.encryptPin;
556
568
  exports.validatePin = aes.validatePin;
569
+ exports.DEFAULT_BIGINT_ID_FIELDS = wsCipher.DEFAULT_BIGINT_ID_FIELDS;
570
+ exports.decodeBase64ToBuffer = wsCipher.decodeBase64ToBuffer;
571
+ exports.decodeEventData = wsCipher.decodeEventData;
572
+ exports.decodeUnit8Array = wsCipher.decodeUnit8Array;
573
+ exports.preParseJsonIds = wsCipher.preParseJsonIds;
557
574
  exports.apiFactory = apiFactory;
558
575
  exports.formatTime = formatTime;
559
576
  exports.generateZaloUUID = generateZaloUUID;
@@ -64,4 +64,17 @@ export declare const ZALO_ENDPOINTS: {
64
64
  readonly LIST: "/api/recentsearch/list";
65
65
  readonly UPDATE: "/api/recentsearch/update";
66
66
  };
67
+ readonly GROUP: {
68
+ readonly GET_LIST_V3: "/api/group/getlg/v3";
69
+ readonly GET_LIST_V4: "/api/group/getlg/v4";
70
+ readonly GET_INFO_V1: "/api/group/getgi";
71
+ readonly GET_INFO_V2: "/api/group/getgi/v2";
72
+ readonly GET_MULTI_INFO_V1: "/api/group/getmg";
73
+ readonly GET_MULTI_INFO_V2: "/api/group/getmg-v2";
74
+ readonly GET_STATUS_MSG: "/api/group/getStatusMsg";
75
+ readonly HISTORY_LEGACY: "/api/group/history";
76
+ readonly CLOUD_MSG_RECENT_V2: "/api/cm/getrecentv2";
77
+ readonly CLOUD_MSG_OLD_V2: "/api/cm/getoldv2";
78
+ readonly CLOUD_MSG_JUMP_V2: "/api/cm/rgetv2";
79
+ };
67
80
  };
@@ -64,4 +64,17 @@ export const ZALO_ENDPOINTS = {
64
64
  LIST: "/api/recentsearch/list",
65
65
  UPDATE: "/api/recentsearch/update",
66
66
  },
67
+ GROUP: {
68
+ GET_LIST_V3: "/api/group/getlg/v3",
69
+ GET_LIST_V4: "/api/group/getlg/v4",
70
+ GET_INFO_V1: "/api/group/getgi",
71
+ GET_INFO_V2: "/api/group/getgi/v2",
72
+ GET_MULTI_INFO_V1: "/api/group/getmg",
73
+ GET_MULTI_INFO_V2: "/api/group/getmg-v2",
74
+ GET_STATUS_MSG: "/api/group/getStatusMsg",
75
+ HISTORY_LEGACY: "/api/group/history",
76
+ CLOUD_MSG_RECENT_V2: "/api/cm/getrecentv2",
77
+ CLOUD_MSG_OLD_V2: "/api/cm/getoldv2",
78
+ CLOUD_MSG_JUMP_V2: "/api/cm/rgetv2",
79
+ },
67
80
  };
@@ -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.2";
13
+ readonly SDK_VERSION: "2.3.4";
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.2",
13
+ SDK_VERSION: "2.3.4",
14
14
  /** Mã zpw_type đặc thù dành riêng cho phân hệ Gọi Thoại & Video (binary C++ ZaloCall) */
15
15
  VOICECALL_API_TYPE: 24,
16
16
  /** Phiên bản mã hóa ZCID (ParamDecryptor) */
package/dist/context.d.ts CHANGED
@@ -154,18 +154,28 @@ export type Options = {
154
154
  apiType: number;
155
155
  apiVersion: number;
156
156
  agent?: Agent;
157
+ /**
158
+ * Bộ điều phối kết nối (Undici `Dispatcher` / `ProxyAgent`) dành cho `globalThis.fetch` mặc định của Node.js 18+.
159
+ */
160
+ dispatcher?: unknown;
157
161
  /**
158
162
  * Optional fetch implementation for polyfills in non-standard environments.
159
163
  * If using proxy, `node-fetch` is highly recommended.
160
164
  */
161
165
  polyfill: typeof fetch;
162
166
  imageMetadataGetter?: ImageMetadataGetter;
167
+ /**
168
+ * Thời gian chờ tối đa cho mỗi HTTP request tính bằng mili-giây (mặc định: `30000`ms = 30s, đặt `0` để tắt).
169
+ */
170
+ requestTimeout?: number;
163
171
  };
164
172
  declare class CallbacksMap extends Map<string, UploadCallback> {
173
+ private timers;
165
174
  /**
166
175
  * @param ttl Time to live in milliseconds. Default is 5 minutes.
167
176
  */
168
177
  set(key: string, value: UploadCallback, ttl?: number): this;
178
+ delete(key: string): boolean;
169
179
  }
170
180
  export type ViewerKeyCacheEntry = {
171
181
  viewer_key: string;
package/dist/context.js CHANGED
@@ -1,15 +1,32 @@
1
1
  import { ZALO_DEFAULTS, ZALO_PROTOCOL } from "./constants/index.js";
2
2
  const _5_MINUTES = ZALO_DEFAULTS.UPLOAD.CALLBACK_TTL_MS;
3
3
  class CallbacksMap extends Map {
4
+ timers = new Map();
4
5
  /**
5
6
  * @param ttl Time to live in milliseconds. Default is 5 minutes.
6
7
  */
7
8
  set(key, value, ttl = _5_MINUTES) {
8
- setTimeout(() => {
9
- this.delete(key);
9
+ const existing = this.timers.get(key);
10
+ if (existing)
11
+ clearTimeout(existing);
12
+ const timer = setTimeout(() => {
13
+ this.timers.delete(key);
14
+ super.delete(key);
10
15
  }, ttl);
16
+ if (typeof timer === "object" && timer !== null && "unref" in timer && typeof timer.unref === "function") {
17
+ timer.unref();
18
+ }
19
+ this.timers.set(key, timer);
11
20
  return super.set(key, value);
12
21
  }
22
+ delete(key) {
23
+ const existing = this.timers.get(key);
24
+ if (existing) {
25
+ clearTimeout(existing);
26
+ this.timers.delete(key);
27
+ }
28
+ return super.delete(key);
29
+ }
13
30
  }
14
31
  export const createContext = (apiType = ZALO_PROTOCOL.API_TYPE, apiVersion = ZALO_PROTOCOL.API_VERSION) => ({
15
32
  API_TYPE: apiType,
@@ -20,6 +37,7 @@ export const createContext = (apiType = ZALO_PROTOCOL.API_TYPE, apiVersion = ZAL
20
37
  checkUpdate: ZALO_DEFAULTS.OPTIONS.CHECK_UPDATE,
21
38
  logging: ZALO_DEFAULTS.OPTIONS.LOGGING,
22
39
  polyfill: global.fetch,
40
+ requestTimeout: 30000,
23
41
  },
24
42
  secretKey: null,
25
43
  });
@@ -1,3 +1,10 @@
1
+ export declare const DEFAULT_BIGINT_ID_FIELDS: readonly ["owner_id", "group_id", "uidFrom", "uidTo", "uid", "ownerId", "destId", "srcId", "fromUid", "threadId", "globalMsgId", "global_msg_id", "globalDelMsgId", "msgId", "refMessageId", "cliMsgId", "client_msg_id", "actionId", "lastActionIdOther", "lastActionId", "resetLastActionId", "lastId", "ids", "topicId", "reminderId", "eventId"];
2
+ /**
3
+ * Tiền xử lý chuỗi JSON trước khi `JSON.parse` (tương đương `preParse(e, t)` trong `app.asar` của Zalo PC).
4
+ * Bọc dấu ngoặc kép quanh các trường định danh 64-bit (`owner_id`, `group_id`, hoặc các số nguyên >= 16 chữ số
5
+ * vượt quá `Number.MAX_SAFE_INTEGER`) để tránh mất 3 chữ số cuối của UID/MsgId do làm tròn IEEE-754.
6
+ */
7
+ export declare function preParseJsonIds(jsonStr: string, alwaysStringFields?: readonly string[], bigIntSafeFields?: readonly string[]): string;
1
8
  export declare function decodeBase64ToBuffer(data: string): Buffer;
2
9
  export declare function decodeUnit8Array(data: Uint8Array): string | null;
3
10
  export declare function decodeEventData(parsed: Record<string, unknown>, cipherKey?: string): Promise<any>;
@@ -2,6 +2,68 @@ import crypto from "node:crypto";
2
2
  import pako from "pako";
3
3
  import JSONBig from "json-bigint";
4
4
  import { ZaloApiError } from "../Errors/index.js";
5
+ const jsonBigString = JSONBig({ storeAsString: true });
6
+ export const DEFAULT_BIGINT_ID_FIELDS = [
7
+ "owner_id",
8
+ "group_id",
9
+ "uidFrom",
10
+ "uidTo",
11
+ "uid",
12
+ "ownerId",
13
+ "destId",
14
+ "srcId",
15
+ "fromUid",
16
+ "threadId",
17
+ "globalMsgId",
18
+ "global_msg_id",
19
+ "globalDelMsgId",
20
+ "msgId",
21
+ "refMessageId",
22
+ "cliMsgId",
23
+ "client_msg_id",
24
+ "actionId",
25
+ "lastActionIdOther",
26
+ "lastActionId",
27
+ "resetLastActionId",
28
+ "lastId",
29
+ "ids",
30
+ "topicId",
31
+ "reminderId",
32
+ "eventId",
33
+ ];
34
+ /**
35
+ * Tiền xử lý chuỗi JSON trước khi `JSON.parse` (tương đương `preParse(e, t)` trong `app.asar` của Zalo PC).
36
+ * Bọc dấu ngoặc kép quanh các trường định danh 64-bit (`owner_id`, `group_id`, hoặc các số nguyên >= 16 chữ số
37
+ * vượt quá `Number.MAX_SAFE_INTEGER`) để tránh mất 3 chữ số cuối của UID/MsgId do làm tròn IEEE-754.
38
+ */
39
+ export function preParseJsonIds(jsonStr, alwaysStringFields = ["owner_id", "group_id"], bigIntSafeFields = DEFAULT_BIGINT_ID_FIELDS) {
40
+ if (!jsonStr || typeof jsonStr !== "string")
41
+ return jsonStr;
42
+ const alwaysSet = new Set(alwaysStringFields);
43
+ const allFields = Array.from(new Set([...alwaysStringFields, ...bigIntSafeFields]));
44
+ if (allFields.length === 0)
45
+ return jsonStr;
46
+ const escapedKeys = allFields.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
47
+ const keyValueRegex = new RegExp(`"(${escapedKeys})"(\\s*:\\s*)(\\d+|\\[\\s*\\d+(?:\\s*,\\s*\\d+)*\\s*\\])`, "g");
48
+ return jsonStr.replace(keyValueRegex, (fullMatch, key, sep, rawVal) => {
49
+ const forceString = alwaysSet.has(key);
50
+ if (rawVal.startsWith("[")) {
51
+ const inner = rawVal.slice(1, -1);
52
+ const parts = inner.split(",").map((part) => {
53
+ const trimmed = part.trim();
54
+ if (forceString || trimmed.length >= 16 || Number(trimmed) > Number.MAX_SAFE_INTEGER) {
55
+ return `"${trimmed}"`;
56
+ }
57
+ return trimmed;
58
+ });
59
+ return `"${key}"${sep}[${parts.join(",")}]`;
60
+ }
61
+ if (forceString || rawVal.length >= 16 || Number(rawVal) > Number.MAX_SAFE_INTEGER) {
62
+ return `"${key}"${sep}"${rawVal}"`;
63
+ }
64
+ return fullMatch;
65
+ });
66
+ }
5
67
  export function decodeBase64ToBuffer(data) {
6
68
  return Buffer.from(data, "base64");
7
69
  }
@@ -23,7 +85,7 @@ export async function decodeEventData(parsed, cipherKey) {
23
85
  const rawData = parsed.data;
24
86
  const encryptType = parsed.encrypt;
25
87
  if (encryptType === 0)
26
- return JSON.parse(rawData);
88
+ return jsonBigString.parse(preParseJsonIds(rawData));
27
89
  const decodedBuffer = decodeBase64ToBuffer(encryptType === 1 ? rawData : decodeURIComponent(rawData));
28
90
  let decryptedBuffer = decodedBuffer;
29
91
  if (encryptType !== 1) {
@@ -48,5 +110,5 @@ export async function decodeEventData(parsed, cipherKey) {
48
110
  const decodedData = decodeUnit8Array(decompressedBuffer);
49
111
  if (!decodedData)
50
112
  return;
51
- return JSONBig.parse(decodedData);
113
+ return jsonBigString.parse(preParseJsonIds(decodedData));
52
114
  }
package/dist/index.d.ts CHANGED
@@ -68,8 +68,27 @@ export type { GetFriendRecommendationsResponse, FriendRecommendationsCollapseMsg
68
68
  export type { GetFriendRequestStatusResponse } from "./apis/friend/getFriendRequestStatus.js";
69
69
  export type { GetFullAvatarResponse } from "./apis/profile/getFullAvatar.js";
70
70
  export type { GetGroupBlockedMemberPayload, GetGroupBlockedMemberResponse } from "./apis/group/getGroupBlockedMember.js";
71
- export type { GetGroupChatHistoryResponse } from "./apis/group/getGroupChatHistory.js";
71
+ export type { GetGroupChatHistoryOptions, GetGroupChatHistoryResponse } from "./apis/group/getGroupChatHistory.js";
72
+ export type { GetGroupHistoryLegacyResponse } from "./apis/group/getGroupHistoryLegacy.js";
72
73
  export type { GroupInfoPendingApprove, GroupInfoResponse } from "./apis/group/getGroupInfo.js";
74
+ export type { GetGroupInfoV2Options, GetGroupInfoV2Response } from "./apis/group/getGroupInfoV2.js";
75
+ export type { GetGroupListV3Options, GetGroupListV3Response } from "./apis/group/getGroupListV3.js";
76
+ export type { GetGroupMessageStatusQueryItem, GetGroupMessageStatusResponse, GroupMessageStatusMemberItem, GroupMessageStatusThreadInfo, } from "./apis/group/getGroupMessageStatus.js";
77
+ export type { CheckQuotaCommunityMemberResponse } from "./apis/group/checkQuotaCommunityMember.js";
78
+ export type { GetE2eeGroupListResponse } from "./apis/group/getE2eeGroupList.js";
79
+ export type { GetMultiGroupCloudMessagesResponse } from "./apis/group/getMultiGroupCloudMessages.js";
80
+ export type { GetGroupCloudMessagePathOptions, GetGroupCloudMessagePathResponse } from "./apis/group/getGroupCloudMessagePath.js";
81
+ export type { AnalyseGroupResponse } from "./apis/group/analyseGroup.js";
82
+ export type { DirectMessageStatusItem, GetDirectMessageStatusResponse } from "./apis/chat/getDirectMessageStatus.js";
83
+ export type { GetSuggestedConversationsResponse } from "./apis/chat/getSuggestedConversations.js";
84
+ export type { UpdatePinConversationsOptions, UpdatePinConversationsResponse } from "./apis/chat/updatePinConversations.js";
85
+ export type { GetFriendProfilesV2Options, GetFriendProfilesV2Response } from "./apis/friend/getFriendProfilesV2.js";
86
+ export type { GetFriendsActivePCWebResponse } from "./apis/friend/getFriendsActivePCWeb.js";
87
+ export type { VerifyFriendOptions, VerifyFriendResponse } from "./apis/friend/verifyFriend.js";
88
+ export type { GetPersonalMediaOptions } from "./apis/media/getPersonalMedia.js";
89
+ export type { GetPersonalMediaPreviewResponse } from "./apis/media/getPersonalMediaPreview.js";
90
+ export type { RemovePersonalMediaOptions, RemovePersonalMediaResponse } from "./apis/media/removePersonalMedia.js";
91
+ export type { ExportedSession } from "./apis/auth/exportSession.js";
73
92
  export type { GetGroupInviteBoxInfoPayload, GetGroupInviteBoxInfoResponse } from "./apis/group/getGroupInviteBoxInfo.js";
74
93
  export type { GetGroupInviteBoxListPayload, GetGroupInviteBoxListResponse } from "./apis/group/getGroupInviteBoxList.js";
75
94
  export type { GetGroupLinkDetailResponse } from "./apis/group/getGroupLinkDetail.js";
@@ -176,7 +195,7 @@ export type { GetBlockFriendsResponse } from "./apis/friend/getBlockFriends.js";
176
195
  export type { GetCloudInfoResponse } from "./apis/zcloud/getCloudInfo.js";
177
196
  export type { GetCloudSettingsResponse } from "./apis/zcloud/getCloudSettings.js";
178
197
  export type { GetCloudViewerKeyResponse } from "./apis/zcloud/getCloudViewerKey.js";
179
- export type { GetConversationMediaOptions, GetConversationMediaResponse } from "./apis/media/getConversationMedia.js";
198
+ export type { GetConversationMediaOptions, GetConversationMediaResponse, MediaStoreItem, MediaStoreType, } from "./apis/media/getConversationMedia.js";
180
199
  export type { GetEmojiStatusConfigResponse } from "./apis/profile/getEmojiStatusConfig.js";
181
200
  export type { GetExtraFriendInfoResponse } from "./apis/friend/getExtraFriendInfo.js";
182
201
  export type { GetExtraGroupInfoResponse } from "./apis/group/getExtraGroupInfo.js";
@@ -221,4 +240,4 @@ export { ChatTTL } from "./apis/chat/updateAutoDeleteChat.js";
221
240
  export { UpdateLangAvailableLanguages } from "./apis/auth/updateLang.js";
222
241
  export { UpdateSettingsType } from "./apis/profile/updateSettings.js";
223
242
  export { CallType } from "./apis/call/sendVoiceCall.js";
224
- export { generateRSAKeyPair, decryptWithPrivateKey, deriveZdbAesKey, decryptZdbBuffer, parseZdbHeader, encryptZCloudAES, decryptZCloudAES, type ZdbTocEntry, } from "./utils.js";
243
+ export { generateRSAKeyPair, decryptWithPrivateKey, deriveZdbAesKey, decryptZdbBuffer, parseZdbHeader, encryptZCloudAES, decryptZCloudAES, preParseJsonIds, type ZdbTocEntry, } from "./utils.js";
package/dist/index.js CHANGED
@@ -16,4 +16,4 @@ export { ChatTTL } from "./apis/chat/updateAutoDeleteChat.js";
16
16
  export { UpdateLangAvailableLanguages } from "./apis/auth/updateLang.js";
17
17
  export { UpdateSettingsType } from "./apis/profile/updateSettings.js";
18
18
  export { CallType } from "./apis/call/sendVoiceCall.js";
19
- export { generateRSAKeyPair, decryptWithPrivateKey, deriveZdbAesKey, decryptZdbBuffer, parseZdbHeader, encryptZCloudAES, decryptZCloudAES, } from "./utils.js";
19
+ export { generateRSAKeyPair, decryptWithPrivateKey, deriveZdbAesKey, decryptZdbBuffer, parseZdbHeader, encryptZCloudAES, decryptZCloudAES, preParseJsonIds, } from "./utils.js";
@@ -21,6 +21,7 @@ export declare class Listener extends EventEmitter<ListenerEvents> {
21
21
  private retryTimeout?;
22
22
  private watchdogTimeout?;
23
23
  private consecutive1006Count;
24
+ private isManualStop;
24
25
  private id;
25
26
  constructor(ctx: ContextSession, urls: string[]);
26
27
  /**
@@ -27,6 +27,7 @@ export class Listener extends EventEmitter {
27
27
  retryTimeout;
28
28
  watchdogTimeout;
29
29
  consecutive1006Count = 0;
30
+ isManualStop = false;
30
31
  id = 0;
31
32
  constructor(ctx, urls) {
32
33
  super();
@@ -152,6 +153,7 @@ export class Listener extends EventEmitter {
152
153
  logger(this.ctx).verbose(`Rotating endpoint to ${this.wsURL}`);
153
154
  }
154
155
  start({ retryOnClose = false } = {}) {
156
+ this.isManualStop = false;
155
157
  if (this.retryTimeout) {
156
158
  clearTimeout(this.retryTimeout);
157
159
  this.retryTimeout = undefined;
@@ -175,6 +177,10 @@ export class Listener extends EventEmitter {
175
177
  t: Date.now(),
176
178
  });
177
179
  const cookie = this.ctx.cookie.getCookieStringSync(ZALO_DOMAINS.CHAT_WEB);
180
+ const wsAgent = this.ctx.options.agent &&
181
+ typeof this.ctx.options.agent.dispatch !== "function"
182
+ ? this.ctx.options.agent
183
+ : undefined;
178
184
  const ws = new WebSocket(this.wsURL, {
179
185
  headers: {
180
186
  "accept-language": getBrowserLanguage(this.ctx.language),
@@ -189,7 +195,7 @@ export class Listener extends EventEmitter {
189
195
  "user-agent": this.userAgent,
190
196
  cookie,
191
197
  },
192
- agent: this.ctx.options.agent,
198
+ ...(wsAgent ? { agent: wsAgent } : {}),
193
199
  });
194
200
  this.ws = ws;
195
201
  ws.onopen = () => {
@@ -202,7 +208,8 @@ export class Listener extends EventEmitter {
202
208
  ws.onclose = (event) => {
203
209
  this.reset();
204
210
  this.emit("disconnected", event.code, event.reason);
205
- const retry = retryOnClose &&
211
+ const retry = !this.isManualStop &&
212
+ retryOnClose &&
206
213
  this.isRetryable(event.code) &&
207
214
  this.canRetry(event.code);
208
215
  if (retry && retryOnClose) {
@@ -288,19 +295,24 @@ export class Listener extends EventEmitter {
288
295
  };
289
296
  }
290
297
  stop() {
291
- if (this.retryTimeout)
298
+ this.isManualStop = true;
299
+ if (this.retryTimeout) {
292
300
  clearTimeout(this.retryTimeout);
293
- if (this.watchdogTimeout)
301
+ this.retryTimeout = undefined;
302
+ }
303
+ if (this.watchdogTimeout) {
294
304
  clearTimeout(this.watchdogTimeout);
305
+ this.watchdogTimeout = undefined;
306
+ }
295
307
  if (this.ws) {
296
- if (this.ws.readyState !== WebSocket.CLOSED) {
308
+ if (this.ws.readyState !== WebSocket.CLOSED && this.ws.readyState !== WebSocket.CLOSING) {
297
309
  this.ws.close(CloseReason.ManualClosure);
298
310
  }
299
311
  }
300
312
  this.reset();
301
313
  }
302
314
  sendWs(payload, requireId = true) {
303
- if (this.ws) {
315
+ if (this.ws && (this.ws.readyState === undefined || this.ws.readyState === WebSocket.OPEN)) {
304
316
  if (requireId)
305
317
  payload.data["req_id"] = `req_${this.id++}`;
306
318
  const encodedData = new TextEncoder().encode(JSON.stringify(payload.data));
package/dist/update.js CHANGED
@@ -6,6 +6,9 @@ const NPM_REGISTRY = ZALO_DOMAINS.NPM_REGISTRY;
6
6
  export async function checkUpdate(ctx) {
7
7
  if (!ctx.options.checkUpdate)
8
8
  return;
9
+ const isAgentUndiciDispatcher = Boolean(ctx.options.agent && typeof ctx.options.agent.dispatch === "function");
10
+ const resolvedDispatcher = ctx.options.dispatcher ?? (isAgentUndiciDispatcher ? ctx.options.agent : undefined);
11
+ const resolvedAgent = ctx.options.agent && !isAgentUndiciDispatcher ? ctx.options.agent : undefined;
9
12
  const _options = {
10
13
  ...(isBun
11
14
  ? {
@@ -13,7 +16,13 @@ export async function checkUpdate(ctx) {
13
16
  // @ts-ignore
14
17
  proxy: ctx.options.agent?.proxy?.href,
15
18
  }
16
- : { agent: ctx.options.agent }),
19
+ : {
20
+ ...(resolvedAgent ? { agent: resolvedAgent } : {}),
21
+ ...(resolvedDispatcher ? { dispatcher: resolvedDispatcher } : {}),
22
+ }),
23
+ ...(typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function"
24
+ ? { signal: AbortSignal.timeout(3000) }
25
+ : {}),
17
26
  };
18
27
  const response = await ctx.options.polyfill(NPM_REGISTRY, _options).catch(() => null);
19
28
  if (!response || !response.ok)
package/dist/utils.js CHANGED
@@ -9,7 +9,7 @@ import { ZaloApiError, ZaloApiMissingImageMetadataGetter } from "./Errors/index.
9
9
  import { FriendEventType } from "./models/FriendEvent.js";
10
10
  import { GroupEventType } from "./models/GroupEvent.js";
11
11
  import { ZALO_DEFAULTS, ZALO_DOMAINS, ZALO_PROTOCOL } from "./constants/index.js";
12
- import { decodeAES, encodeAES } from "./crypto/index.js";
12
+ import { decodeAES, encodeAES, preParseJsonIds } from "./crypto/index.js";
13
13
  export * from "./constants/index.js";
14
14
  export * from "./crypto/index.js";
15
15
  export * from "./core/index.js";
@@ -161,8 +161,20 @@ export async function request(ctx, url, options, raw = false) {
161
161
  options = { ...(options || {}), headers: defaultHeaders };
162
162
  }
163
163
  }
164
+ const timeoutMs = ctx.options?.requestTimeout;
165
+ const defaultSignal = !options?.signal &&
166
+ typeof timeoutMs === "number" &&
167
+ timeoutMs > 0 &&
168
+ typeof AbortSignal !== "undefined" &&
169
+ typeof AbortSignal.timeout === "function"
170
+ ? AbortSignal.timeout(timeoutMs)
171
+ : undefined;
172
+ const isAgentUndiciDispatcher = Boolean(ctx.options.agent && typeof ctx.options.agent.dispatch === "function");
173
+ const resolvedDispatcher = ctx.options.dispatcher ?? (isAgentUndiciDispatcher ? ctx.options.agent : undefined);
174
+ const resolvedAgent = ctx.options.agent && !isAgentUndiciDispatcher ? ctx.options.agent : undefined;
164
175
  const _options = {
165
176
  ...(options ?? {}),
177
+ ...(defaultSignal ? { signal: defaultSignal } : {}),
166
178
  ...(isBun
167
179
  ? {
168
180
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -170,8 +182,8 @@ export async function request(ctx, url, options, raw = false) {
170
182
  proxy: ctx.options.agent?.proxy?.href,
171
183
  }
172
184
  : {
173
- agent: ctx.options.agent,
174
- dispatcher: ctx.options.agent,
185
+ ...(resolvedAgent ? { agent: resolvedAgent } : {}),
186
+ ...(resolvedDispatcher ? { dispatcher: resolvedDispatcher } : {}),
175
187
  }),
176
188
  };
177
189
  const response = await ctx.options.polyfill(url, _options);
@@ -476,7 +488,8 @@ export async function handleZaloResponse(ctx, response, isEncrypted = true) {
476
488
  result.data = {};
477
489
  return result;
478
490
  }
479
- const decodedData = isEncrypted ? JSON.parse(decodedStr) : jsonData;
491
+ const safeJsonStr = isEncrypted ? preParseJsonIds(decodedStr) : null;
492
+ const decodedData = isEncrypted ? JSON.parse(safeJsonStr) : jsonData;
480
493
  if (decodedData.error_code != 0) {
481
494
  result.error = {
482
495
  message: decodedData.error_message,
package/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from "./dist";
1
+ export * from "./dist/index.js";
2
2
  export as namespace Zalo;
package/package.json CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "@softtynet/zca-js",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "Zalo API client library for Node.js and Bun, maintained by Softtynet.",
5
- "main": "dist/index.js",
5
+ "main": "dist/cjs/index.cjs",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
6
8
  "type": "module",
7
9
  "files": [
8
10
  "dist",
@@ -12,8 +14,9 @@
12
14
  ],
13
15
  "exports": {
14
16
  ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js",
15
19
  "require": "./dist/cjs/index.cjs",
16
- "types": "./index.d.ts",
17
20
  "default": "./dist/index.js"
18
21
  }
19
22
  },
@@ -23,8 +26,14 @@
23
26
  "build:esm": "tsc",
24
27
  "build:cjs": "rollup -c rollup.config.js",
25
28
  "build": "npm run build:clean && npm run build:esm && npm run build:cjs",
26
- "test:verify": "npx --yes tsx test/verify_upgrades.ts",
29
+ "typecheck": "tsc --noEmit && tsc -p test/tsconfig.json --noEmit && tsc -p examples/tsconfig.json --noEmit && tsc -p docs/tsconfig.json --noEmit",
30
+ "test:verify": "npm run typecheck && npx --yes tsx test/verify_upgrades.ts",
27
31
  "test:live": "npx --yes tsx test/verify_live.ts",
32
+ "docs:gen": "node scripts/generateDocs.js",
33
+ "docs:dev": "node scripts/generateDocs.js && vitepress dev docs",
34
+ "docs:build": "node scripts/generateDocs.js && vitepress build docs",
35
+ "docs:preview": "vitepress preview docs",
36
+ "docs:deploy": "node scripts/deployDocs.js",
28
37
  "prettier": "prettier --write .",
29
38
  "lint": "eslint .",
30
39
  "lint:fix": "eslint . --fix",
@@ -79,7 +88,8 @@
79
88
  "rimraf": "^5.0.10",
80
89
  "rollup": "^4.24.0",
81
90
  "typescript": "^5.5.3",
82
- "typescript-eslint": "^8.41.0"
91
+ "typescript-eslint": "^8.41.0",
92
+ "vitepress": "^1.6.4"
83
93
  },
84
94
  "lint-staged": {
85
95
  "*.{js,ts}": "eslint"