@softtynet/zca-js 2.3.3 → 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 (74) 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/getGroupCloudMessagePath.d.ts +19 -0
  23. package/dist/apis/group/getGroupCloudMessagePath.js +51 -0
  24. package/dist/apis/group/getMultiGroupCloudMessages.d.ts +5 -0
  25. package/dist/apis/group/getMultiGroupCloudMessages.js +29 -0
  26. package/dist/apis/media/getPersonalMedia.d.ts +8 -0
  27. package/dist/apis/media/getPersonalMedia.js +31 -0
  28. package/dist/apis/media/getPersonalMediaPreview.d.ts +3 -0
  29. package/dist/apis/media/getPersonalMediaPreview.js +24 -0
  30. package/dist/apis/media/removePersonalMedia.d.ts +9 -0
  31. package/dist/apis/media/removePersonalMedia.js +28 -0
  32. package/dist/apis.d.ts +45 -0
  33. package/dist/apis.js +75 -0
  34. package/dist/cjs/apis/auth/exportSession.cjs +22 -0
  35. package/dist/cjs/apis/chat/getDirectMessageStatus.cjs +58 -0
  36. package/dist/cjs/apis/chat/getSuggestedConversations.cjs +29 -0
  37. package/dist/cjs/apis/chat/updatePinConversations.cjs +45 -0
  38. package/dist/cjs/apis/friend/getFriendProfilesV2.cjs +43 -0
  39. package/dist/cjs/apis/friend/getFriendsActivePCWeb.cjs +20 -0
  40. package/dist/cjs/apis/friend/verifyFriend.cjs +42 -0
  41. package/dist/cjs/apis/group/analyseGroup.cjs +33 -0
  42. package/dist/cjs/apis/group/checkQuotaCommunityMember.cjs +39 -0
  43. package/dist/cjs/apis/group/getE2eeGroupList.cjs +20 -0
  44. package/dist/cjs/apis/group/getGroupChatHistory.cjs +8 -7
  45. package/dist/cjs/apis/group/getGroupCloudMessagePath.cjs +69 -0
  46. package/dist/cjs/apis/group/getGroupHistoryLegacy.cjs +7 -6
  47. package/dist/cjs/apis/group/getGroupMessageStatus.cjs +7 -6
  48. package/dist/cjs/apis/group/getMultiGroupCloudMessages.cjs +34 -0
  49. package/dist/cjs/apis/media/getPersonalMedia.cjs +36 -0
  50. package/dist/cjs/apis/media/getPersonalMediaPreview.cjs +29 -0
  51. package/dist/cjs/apis/media/removePersonalMedia.cjs +33 -0
  52. package/dist/cjs/apis.cjs +75 -0
  53. package/dist/cjs/constants/protocol.cjs +1 -1
  54. package/dist/cjs/context.cjs +20 -2
  55. package/dist/cjs/core/zcloud-client.cjs +1 -2
  56. package/dist/cjs/crypto/ws-cipher.cjs +66 -2
  57. package/dist/cjs/index.cjs +9 -2
  58. package/dist/cjs/realtime/listener.cjs +18 -6
  59. package/dist/cjs/update.cjs +10 -1
  60. package/dist/cjs/utils.cjs +21 -68
  61. package/dist/constants/protocol.d.ts +1 -1
  62. package/dist/constants/protocol.js +1 -1
  63. package/dist/context.d.ts +10 -0
  64. package/dist/context.js +20 -2
  65. package/dist/crypto/ws-cipher.d.ts +7 -0
  66. package/dist/crypto/ws-cipher.js +64 -2
  67. package/dist/index.d.ts +15 -0
  68. package/dist/realtime/listener.d.ts +1 -0
  69. package/dist/realtime/listener.js +18 -6
  70. package/dist/update.js +10 -1
  71. package/dist/utils.d.ts +0 -7
  72. package/dist/utils.js +15 -64
  73. package/index.d.ts +1 -1
  74. 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);
@@ -452,67 +463,6 @@ function getFriendEventType(act) {
452
463
  return FriendEvent.FriendEventType.PIN_CREATE;
453
464
  return FriendEvent.FriendEventType.UNKNOWN;
454
465
  }
455
- const DEFAULT_BIGINT_ID_FIELDS = [
456
- "owner_id",
457
- "group_id",
458
- "uidFrom",
459
- "uidTo",
460
- "uid",
461
- "ownerId",
462
- "destId",
463
- "srcId",
464
- "fromUid",
465
- "threadId",
466
- "globalMsgId",
467
- "global_msg_id",
468
- "globalDelMsgId",
469
- "msgId",
470
- "refMessageId",
471
- "cliMsgId",
472
- "client_msg_id",
473
- "actionId",
474
- "lastActionIdOther",
475
- "lastActionId",
476
- "resetLastActionId",
477
- "lastId",
478
- "ids",
479
- "topicId",
480
- "reminderId",
481
- "eventId",
482
- ];
483
- /**
484
- * Tiền xử lý chuỗi JSON trước khi `JSON.parse` (tương đương `preParse(e, t)` trong `app.asar` của Zalo PC).
485
- * Bọc dấu ngoặc kép quanh các trường định danh 64-bit (`owner_id`, `group_id`, hoặc các số nguyên >= 16 chữ số
486
- * vượt quá `Number.MAX_SAFE_INTEGER`) để tránh mất 3 chữ số cuối của UID/MsgId do làm tròn IEEE-754.
487
- */
488
- function preParseJsonIds(jsonStr, alwaysStringFields = ["owner_id", "group_id"], bigIntSafeFields = DEFAULT_BIGINT_ID_FIELDS) {
489
- if (!jsonStr || typeof jsonStr !== "string")
490
- return jsonStr;
491
- const alwaysSet = new Set(alwaysStringFields);
492
- const allFields = Array.from(new Set([...alwaysStringFields, ...bigIntSafeFields]));
493
- if (allFields.length === 0)
494
- return jsonStr;
495
- const escapedKeys = allFields.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
496
- const keyValueRegex = new RegExp(`"(${escapedKeys})"(\\s*:\\s*)(\\d+|\\[\\s*\\d+(?:\\s*,\\s*\\d+)*\\s*\\])`, "g");
497
- return jsonStr.replace(keyValueRegex, (fullMatch, key, sep, rawVal) => {
498
- const forceString = alwaysSet.has(key);
499
- if (rawVal.startsWith("[")) {
500
- const inner = rawVal.slice(1, -1);
501
- const parts = inner.split(",").map((part) => {
502
- const trimmed = part.trim();
503
- if (forceString || trimmed.length >= 16 || Number(trimmed) > Number.MAX_SAFE_INTEGER) {
504
- return `"${trimmed}"`;
505
- }
506
- return trimmed;
507
- });
508
- return `"${key}"${sep}[${parts.join(",")}]`;
509
- }
510
- if (forceString || rawVal.length >= 16 || Number(rawVal) > Number.MAX_SAFE_INTEGER) {
511
- return `"${key}"${sep}"${rawVal}"`;
512
- }
513
- return fullMatch;
514
- });
515
- }
516
466
  async function handleZaloResponse(ctx, response, isEncrypted = true) {
517
467
  const result = {
518
468
  data: null,
@@ -542,7 +492,7 @@ async function handleZaloResponse(ctx, response, isEncrypted = true) {
542
492
  result.data = {};
543
493
  return result;
544
494
  }
545
- const safeJsonStr = isEncrypted ? preParseJsonIds(decodedStr) : null;
495
+ const safeJsonStr = isEncrypted ? wsCipher.preParseJsonIds(decodedStr) : null;
546
496
  const decodedData = isEncrypted ? JSON.parse(safeJsonStr) : jsonData;
547
497
  if (decodedData.error_code != 0) {
548
498
  result.error = {
@@ -616,7 +566,11 @@ exports.decryptResp = aes.decryptResp;
616
566
  exports.encodeAES = aes.encodeAES;
617
567
  exports.encryptPin = aes.encryptPin;
618
568
  exports.validatePin = aes.validatePin;
619
- exports.DEFAULT_BIGINT_ID_FIELDS = DEFAULT_BIGINT_ID_FIELDS;
569
+ exports.DEFAULT_BIGINT_ID_FIELDS = wsCipher.DEFAULT_BIGINT_ID_FIELDS;
570
+ exports.decodeBase64ToBuffer = wsCipher.decodeBase64ToBuffer;
571
+ exports.decodeEventData = wsCipher.decodeEventData;
572
+ exports.decodeUnit8Array = wsCipher.decodeUnit8Array;
573
+ exports.preParseJsonIds = wsCipher.preParseJsonIds;
620
574
  exports.apiFactory = apiFactory;
621
575
  exports.formatTime = formatTime;
622
576
  exports.generateZaloUUID = generateZaloUUID;
@@ -642,7 +596,6 @@ exports.isBun = isBun;
642
596
  exports.logger = logger;
643
597
  exports.makeURL = makeURL;
644
598
  exports.normalizeHolderName = normalizeHolderName;
645
- exports.preParseJsonIds = preParseJsonIds;
646
599
  exports.removeUndefinedKeys = removeUndefinedKeys;
647
600
  exports.request = request;
648
601
  exports.resolveResponse = resolveResponse;
@@ -10,7 +10,7 @@ export declare const ZALO_PROTOCOL: {
10
10
  readonly API_VERSION: 689;
11
11
  readonly DEFAULT_API_VERSION: 689;
12
12
  /** Phiên bản SDK hiện tại */
13
- readonly SDK_VERSION: "2.3.3";
13
+ readonly SDK_VERSION: "2.3.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.3",
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
@@ -74,6 +74,21 @@ export type { GroupInfoPendingApprove, GroupInfoResponse } from "./apis/group/ge
74
74
  export type { GetGroupInfoV2Options, GetGroupInfoV2Response } from "./apis/group/getGroupInfoV2.js";
75
75
  export type { GetGroupListV3Options, GetGroupListV3Response } from "./apis/group/getGroupListV3.js";
76
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";
77
92
  export type { GetGroupInviteBoxInfoPayload, GetGroupInviteBoxInfoResponse } from "./apis/group/getGroupInviteBoxInfo.js";
78
93
  export type { GetGroupInviteBoxListPayload, GetGroupInviteBoxListResponse } from "./apis/group/getGroupInviteBoxList.js";
79
94
  export type { GetGroupLinkDetailResponse } from "./apis/group/getGroupLinkDetail.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.d.ts CHANGED
@@ -112,13 +112,6 @@ export type ZaloResponse<T> = {
112
112
  code?: number;
113
113
  } | null;
114
114
  };
115
- 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"];
116
- /**
117
- * 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).
118
- * 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ố
119
- * 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.
120
- */
121
- export declare function preParseJsonIds(jsonStr: string, alwaysStringFields?: readonly string[], bigIntSafeFields?: readonly string[]): string;
122
115
  export declare function handleZaloResponse<T = unknown>(ctx: ContextSession, response: Response, isEncrypted?: boolean): Promise<ZaloResponse<T>>;
123
116
  export declare function resolveResponse<T = unknown>(ctx: ContextSession, res: Response, cb?: (result: ZaloResponse<unknown>) => T, isEncrypted?: boolean): Promise<T>;
124
117
  export type FactoryUtils<T> = {
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);
@@ -447,67 +459,6 @@ export function getFriendEventType(act) {
447
459
  return FriendEventType.PIN_CREATE;
448
460
  return FriendEventType.UNKNOWN;
449
461
  }
450
- export const DEFAULT_BIGINT_ID_FIELDS = [
451
- "owner_id",
452
- "group_id",
453
- "uidFrom",
454
- "uidTo",
455
- "uid",
456
- "ownerId",
457
- "destId",
458
- "srcId",
459
- "fromUid",
460
- "threadId",
461
- "globalMsgId",
462
- "global_msg_id",
463
- "globalDelMsgId",
464
- "msgId",
465
- "refMessageId",
466
- "cliMsgId",
467
- "client_msg_id",
468
- "actionId",
469
- "lastActionIdOther",
470
- "lastActionId",
471
- "resetLastActionId",
472
- "lastId",
473
- "ids",
474
- "topicId",
475
- "reminderId",
476
- "eventId",
477
- ];
478
- /**
479
- * 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).
480
- * 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ố
481
- * 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.
482
- */
483
- export function preParseJsonIds(jsonStr, alwaysStringFields = ["owner_id", "group_id"], bigIntSafeFields = DEFAULT_BIGINT_ID_FIELDS) {
484
- if (!jsonStr || typeof jsonStr !== "string")
485
- return jsonStr;
486
- const alwaysSet = new Set(alwaysStringFields);
487
- const allFields = Array.from(new Set([...alwaysStringFields, ...bigIntSafeFields]));
488
- if (allFields.length === 0)
489
- return jsonStr;
490
- const escapedKeys = allFields.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
491
- const keyValueRegex = new RegExp(`"(${escapedKeys})"(\\s*:\\s*)(\\d+|\\[\\s*\\d+(?:\\s*,\\s*\\d+)*\\s*\\])`, "g");
492
- return jsonStr.replace(keyValueRegex, (fullMatch, key, sep, rawVal) => {
493
- const forceString = alwaysSet.has(key);
494
- if (rawVal.startsWith("[")) {
495
- const inner = rawVal.slice(1, -1);
496
- const parts = inner.split(",").map((part) => {
497
- const trimmed = part.trim();
498
- if (forceString || trimmed.length >= 16 || Number(trimmed) > Number.MAX_SAFE_INTEGER) {
499
- return `"${trimmed}"`;
500
- }
501
- return trimmed;
502
- });
503
- return `"${key}"${sep}[${parts.join(",")}]`;
504
- }
505
- if (forceString || rawVal.length >= 16 || Number(rawVal) > Number.MAX_SAFE_INTEGER) {
506
- return `"${key}"${sep}"${rawVal}"`;
507
- }
508
- return fullMatch;
509
- });
510
- }
511
462
  export async function handleZaloResponse(ctx, response, isEncrypted = true) {
512
463
  const result = {
513
464
  data: null,
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.3",
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"