@softtynet/zca-js 2.1.16 → 2.1.18
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.
- package/README.md +39 -0
- package/dist/apis/decryptGids.d.ts +4 -0
- package/dist/apis/decryptGids.js +28 -0
- package/dist/apis/downloadTransferSync.d.ts +6 -0
- package/dist/apis/downloadTransferSync.js +30 -0
- package/dist/apis/getGroupChatHistory.d.ts +13 -3
- package/dist/apis/getGroupChatHistory.js +50 -22
- package/dist/apis/listen.d.ts +6 -0
- package/dist/apis/listen.js +80 -18
- package/dist/apis/login.js +1 -1
- package/dist/apis/loginQR.js +6 -21
- package/dist/apis/preloadConversations.d.ts +10 -0
- package/dist/apis/preloadConversations.js +23 -0
- package/dist/apis/requestSyncFromPhone.d.ts +4 -0
- package/dist/apis/requestSyncFromPhone.js +27 -0
- package/dist/apis.d.ts +8 -0
- package/dist/apis.js +8 -0
- package/dist/cjs/apis/decryptGids.cjs +33 -0
- package/dist/cjs/apis/downloadTransferSync.cjs +35 -0
- package/dist/cjs/apis/getGroupChatHistory.cjs +50 -22
- package/dist/cjs/apis/listen.cjs +79 -17
- package/dist/cjs/apis/login.cjs +1 -1
- package/dist/cjs/apis/loginQR.cjs +5 -20
- package/dist/cjs/apis/preloadConversations.cjs +28 -0
- package/dist/cjs/apis/requestSyncFromPhone.cjs +32 -0
- package/dist/cjs/apis.cjs +8 -0
- package/dist/cjs/context.cjs +1 -1
- package/dist/cjs/utils.cjs +49 -17
- package/dist/cjs/zalo.cjs +2 -1
- package/dist/context.js +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/utils.d.ts +28 -7
- package/dist/utils.js +47 -17
- package/dist/zalo.js +2 -1
- package/package.json +1 -1
package/dist/apis.js
CHANGED
|
@@ -21,6 +21,7 @@ import { createNoteFactory } from "./apis/createNote.js";
|
|
|
21
21
|
import { createPollFactory } from "./apis/createPoll.js";
|
|
22
22
|
import { createProductCatalogFactory } from "./apis/createProductCatalog.js";
|
|
23
23
|
import { createReminderFactory } from "./apis/createReminder.js";
|
|
24
|
+
import { decryptGidsFactory } from "./apis/decryptGids.js";
|
|
24
25
|
import { deleteAutoReplyFactory } from "./apis/deleteAutoReply.js";
|
|
25
26
|
import { deleteAvatarFactory } from "./apis/deleteAvatar.js";
|
|
26
27
|
import { deleteCatalogFactory } from "./apis/deleteCatalog.js";
|
|
@@ -30,6 +31,7 @@ import { deleteMessageFactory } from "./apis/deleteMessage.js";
|
|
|
30
31
|
import { deleteProductCatalogFactory } from "./apis/deleteProductCatalog.js";
|
|
31
32
|
import { disableGroupLinkFactory } from "./apis/disableGroupLink.js";
|
|
32
33
|
import { disperseGroupFactory } from "./apis/disperseGroup.js";
|
|
34
|
+
import { downloadTransferSyncFactory } from "./apis/downloadTransferSync.js";
|
|
33
35
|
import { editNoteFactory } from "./apis/editNote.js";
|
|
34
36
|
import { editReminderFactory } from "./apis/editReminder.js";
|
|
35
37
|
import { enableGroupLinkFactory } from "./apis/enableGroupLink.js";
|
|
@@ -94,6 +96,7 @@ import { lastOnlineFactory } from "./apis/lastOnline.js";
|
|
|
94
96
|
import { leaveGroupFactory } from "./apis/leaveGroup.js";
|
|
95
97
|
import { lockPollFactory } from "./apis/lockPoll.js";
|
|
96
98
|
import { parseLinkFactory } from "./apis/parseLink.js";
|
|
99
|
+
import { preloadConversationsFactory } from "./apis/preloadConversations.js";
|
|
97
100
|
import { rejectFriendRequestFactory } from "./apis/rejectFriendRequest.js";
|
|
98
101
|
import { removeFriendFactory } from "./apis/removeFriend.js";
|
|
99
102
|
import { removeFriendAliasFactory } from "./apis/removeFriendAlias.js";
|
|
@@ -103,6 +106,7 @@ import { removeQuickMessageFactory } from "./apis/removeQuickMessage.js";
|
|
|
103
106
|
import { removeReminderFactory } from "./apis/removeReminder.js";
|
|
104
107
|
import { removeUnreadMarkFactory } from "./apis/removeUnreadMark.js";
|
|
105
108
|
import { removeUserFromGroupFactory } from "./apis/removeUserFromGroup.js";
|
|
109
|
+
import { requestSyncFromPhoneFactory } from "./apis/requestSyncFromPhone.js";
|
|
106
110
|
import { resetHiddenConversPinFactory } from "./apis/resetHiddenConversPin.js";
|
|
107
111
|
import { reuseAvatarFactory } from "./apis/reuseAvatar.js";
|
|
108
112
|
import { reviewPendingMemberRequestFactory } from "./apis/reviewPendingMemberRequest.js";
|
|
@@ -171,6 +175,7 @@ export class API {
|
|
|
171
175
|
this.createPoll = createPollFactory(ctx, this);
|
|
172
176
|
this.createProductCatalog = createProductCatalogFactory(ctx, this);
|
|
173
177
|
this.createReminder = createReminderFactory(ctx, this);
|
|
178
|
+
this.decryptGids = decryptGidsFactory(ctx, this);
|
|
174
179
|
this.deleteAutoReply = deleteAutoReplyFactory(ctx, this);
|
|
175
180
|
this.deleteAvatar = deleteAvatarFactory(ctx, this);
|
|
176
181
|
this.deleteCatalog = deleteCatalogFactory(ctx, this);
|
|
@@ -180,6 +185,7 @@ export class API {
|
|
|
180
185
|
this.deleteProductCatalog = deleteProductCatalogFactory(ctx, this);
|
|
181
186
|
this.disableGroupLink = disableGroupLinkFactory(ctx, this);
|
|
182
187
|
this.disperseGroup = disperseGroupFactory(ctx, this);
|
|
188
|
+
this.downloadTransferSync = downloadTransferSyncFactory(ctx, this);
|
|
183
189
|
this.editNote = editNoteFactory(ctx, this);
|
|
184
190
|
this.editReminder = editReminderFactory(ctx, this);
|
|
185
191
|
this.enableGroupLink = enableGroupLinkFactory(ctx, this);
|
|
@@ -244,6 +250,7 @@ export class API {
|
|
|
244
250
|
this.leaveGroup = leaveGroupFactory(ctx, this);
|
|
245
251
|
this.lockPoll = lockPollFactory(ctx, this);
|
|
246
252
|
this.parseLink = parseLinkFactory(ctx, this);
|
|
253
|
+
this.preloadConversations = preloadConversationsFactory(ctx, this);
|
|
247
254
|
this.rejectFriendRequest = rejectFriendRequestFactory(ctx, this);
|
|
248
255
|
this.removeFriend = removeFriendFactory(ctx, this);
|
|
249
256
|
this.removeFriendAlias = removeFriendAliasFactory(ctx, this);
|
|
@@ -253,6 +260,7 @@ export class API {
|
|
|
253
260
|
this.removeReminder = removeReminderFactory(ctx, this);
|
|
254
261
|
this.removeUnreadMark = removeUnreadMarkFactory(ctx, this);
|
|
255
262
|
this.removeUserFromGroup = removeUserFromGroupFactory(ctx, this);
|
|
263
|
+
this.requestSyncFromPhone = requestSyncFromPhoneFactory(ctx, this);
|
|
256
264
|
this.resetHiddenConversPin = resetHiddenConversPinFactory(ctx, this);
|
|
257
265
|
this.reuseAvatar = reuseAvatarFactory(ctx, this);
|
|
258
266
|
this.reviewPendingMemberRequest = reviewPendingMemberRequestFactory(ctx, this);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ZaloApiError = require('../Errors/ZaloApiError.cjs');
|
|
4
|
+
var utils = require('../utils.cjs');
|
|
5
|
+
|
|
6
|
+
const decryptGidsFactory = utils.apiFactory()((api, ctx, utils) => {
|
|
7
|
+
const serviceURL = utils.makeURL(`${api.zpwServiceMap.profile[0]}/api/gid/decrypt`);
|
|
8
|
+
/**
|
|
9
|
+
* Giải mã danh sách Global ID (g_...) ẩn danh thành User ID thật
|
|
10
|
+
*
|
|
11
|
+
* @param gids Danh sách Global ID (hoặc 1 GID đơn lẻ)
|
|
12
|
+
* @throws {ZaloApiError}
|
|
13
|
+
*/
|
|
14
|
+
return async function decryptGids(gids) {
|
|
15
|
+
if (!gids)
|
|
16
|
+
throw new ZaloApiError.ZaloApiError("Missing gids");
|
|
17
|
+
if (!Array.isArray(gids))
|
|
18
|
+
gids = [gids];
|
|
19
|
+
const params = {
|
|
20
|
+
gids,
|
|
21
|
+
imei: ctx.imei,
|
|
22
|
+
};
|
|
23
|
+
const encryptedParams = utils.encodeAES(JSON.stringify(params));
|
|
24
|
+
if (!encryptedParams)
|
|
25
|
+
throw new ZaloApiError.ZaloApiError("Failed to encrypt params");
|
|
26
|
+
const response = await utils.request(utils.makeURL(serviceURL, { params: encryptedParams }), {
|
|
27
|
+
method: "GET",
|
|
28
|
+
});
|
|
29
|
+
return utils.resolve(response);
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
exports.decryptGidsFactory = decryptGidsFactory;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ZaloApiError = require('../Errors/ZaloApiError.cjs');
|
|
4
|
+
var utils = require('../utils.cjs');
|
|
5
|
+
|
|
6
|
+
const downloadTransferSyncFactory = utils.apiFactory()((api, ctx, utils) => {
|
|
7
|
+
/**
|
|
8
|
+
* Tải khối dữ liệu snapshot đồng bộ tin nhắn từ máy chủ transfersync.zaloapp.com
|
|
9
|
+
*
|
|
10
|
+
* @param options URL đầy đủ hoặc object chứa { key, exp, sig }
|
|
11
|
+
* @throws {ZaloApiError}
|
|
12
|
+
*/
|
|
13
|
+
return async function downloadTransferSync(options) {
|
|
14
|
+
let downloadUrl = "";
|
|
15
|
+
if (typeof options === "string") {
|
|
16
|
+
downloadUrl = options;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
if (!options.key || !options.exp || !options.sig) {
|
|
20
|
+
throw new ZaloApiError.ZaloApiError("Missing key, exp or sig in options");
|
|
21
|
+
}
|
|
22
|
+
downloadUrl = `https://transfersync.zaloapp.com/api/transfer-sync-v2/download?key=${options.key}&exp=${options.exp}&sig=${options.sig}`;
|
|
23
|
+
}
|
|
24
|
+
const response = await utils.request(downloadUrl, {
|
|
25
|
+
method: "GET",
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
throw new ZaloApiError.ZaloApiError(`Failed to download transfer snapshot: ${response.statusText} (${response.status})`);
|
|
29
|
+
}
|
|
30
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
31
|
+
return Buffer.from(arrayBuffer);
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
exports.downloadTransferSyncFactory = downloadTransferSyncFactory;
|
|
@@ -14,36 +14,64 @@ require('../models/Reminder.cjs');
|
|
|
14
14
|
require('../models/ZBusiness.cjs');
|
|
15
15
|
|
|
16
16
|
const getGroupChatHistoryFactory = utils.apiFactory()((api, ctx, utils) => {
|
|
17
|
-
const serviceURL = utils.makeURL(`${api.zpwServiceMap.
|
|
17
|
+
const serviceURL = utils.makeURL(`${api.zpwServiceMap.group_cloud_message[0]}/api/cm/getrecentv2`);
|
|
18
18
|
/**
|
|
19
|
-
* Get group chat history
|
|
19
|
+
* Get group chat history from Zalo Cloud Message (newest first)
|
|
20
20
|
*
|
|
21
21
|
* @param groupId group id
|
|
22
|
-
* @param count count of messages to return (default: 50)
|
|
22
|
+
* @param count count of messages to return (default: 50, max safe limit: 200)
|
|
23
23
|
*
|
|
24
24
|
* @throws {ZaloApiError}
|
|
25
25
|
*/
|
|
26
26
|
return async function getGroupChatHistory(groupId, count = 50) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
27
|
+
if (!groupId)
|
|
28
|
+
throw new ZaloApiError.ZaloApiError("Missing groupId");
|
|
29
|
+
if (groupId.startsWith("g"))
|
|
30
|
+
groupId = groupId.slice(1);
|
|
31
|
+
const safeCount = Math.min(Math.max(count, 1), 200);
|
|
32
|
+
const messages = [];
|
|
33
|
+
let cursor = 0;
|
|
34
|
+
let lastPage = null;
|
|
35
|
+
let pageCount = 0;
|
|
36
|
+
while (messages.length < safeCount && pageCount < 10) {
|
|
37
|
+
pageCount++;
|
|
38
|
+
const params = {
|
|
39
|
+
groupId,
|
|
40
|
+
globalMsgId: cursor,
|
|
41
|
+
count: Math.min(50, safeCount - messages.length),
|
|
42
|
+
msgIds: [],
|
|
43
|
+
imei: ctx.imei,
|
|
44
|
+
src: 3,
|
|
45
|
+
};
|
|
46
|
+
const encryptedParams = utils.encodeAES(JSON.stringify(params));
|
|
47
|
+
if (!encryptedParams)
|
|
48
|
+
throw new ZaloApiError.ZaloApiError("Failed to encrypt params");
|
|
49
|
+
const response = await utils.request(utils.makeURL(serviceURL, { params: encryptedParams, nretry: 0 }), {
|
|
50
|
+
method: "GET",
|
|
51
|
+
});
|
|
52
|
+
lastPage = await utils.resolve(response, (result) => {
|
|
53
|
+
let data = result.data;
|
|
54
|
+
if (typeof data === "string") {
|
|
55
|
+
data = JSON.parse(data);
|
|
56
|
+
}
|
|
57
|
+
return data;
|
|
58
|
+
});
|
|
59
|
+
if (!lastPage || !Array.isArray(lastPage.groupMsgs))
|
|
60
|
+
break;
|
|
61
|
+
for (const rawMsg of lastPage.groupMsgs) {
|
|
62
|
+
if (messages.length >= safeCount)
|
|
63
|
+
break;
|
|
64
|
+
if (messages.some((m) => m.msgId === rawMsg.msgId))
|
|
65
|
+
continue;
|
|
66
|
+
messages.push(rawMsg);
|
|
41
67
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
68
|
+
const nextCursor = Number(lastPage.lastMsgId);
|
|
69
|
+
if (!lastPage.hasMore || !nextCursor || nextCursor === cursor)
|
|
70
|
+
break;
|
|
71
|
+
cursor = nextCursor;
|
|
72
|
+
}
|
|
73
|
+
const groupMsgs = messages.map((data) => new Message.GroupMessage(ctx.uid, data));
|
|
74
|
+
return Object.assign({}, lastPage || {}, { groupMsgs });
|
|
47
75
|
};
|
|
48
76
|
});
|
|
49
77
|
|
package/dist/cjs/apis/listen.cjs
CHANGED
|
@@ -31,6 +31,7 @@ class Listener extends EventEmitter {
|
|
|
31
31
|
super();
|
|
32
32
|
this.ctx = ctx;
|
|
33
33
|
this.urls = urls;
|
|
34
|
+
this.consecutive1006Count = 0;
|
|
34
35
|
this.id = 0;
|
|
35
36
|
if (!ctx.cookie)
|
|
36
37
|
throw new ZaloApiError.ZaloApiError("Cookie is not available");
|
|
@@ -82,26 +83,71 @@ class Listener extends EventEmitter {
|
|
|
82
83
|
onMessage(cb) {
|
|
83
84
|
this.onMessageCallback = cb;
|
|
84
85
|
}
|
|
86
|
+
isRetryable(code) {
|
|
87
|
+
return (code === exports.CloseReason.AbnormalClosure ||
|
|
88
|
+
this.ctx.settings.features.socket.close_and_retry_codes.includes(code));
|
|
89
|
+
}
|
|
85
90
|
canRetry(code) {
|
|
86
|
-
|
|
91
|
+
var _a;
|
|
92
|
+
if (!this.isRetryable(code))
|
|
87
93
|
return false;
|
|
88
|
-
|
|
94
|
+
const retry = (_a = this.retryCount[code.toString()]) !== null && _a !== void 0 ? _a : this.retryCount["internal"];
|
|
95
|
+
if (!retry || retry.count >= retry.max)
|
|
89
96
|
return false;
|
|
90
|
-
|
|
91
|
-
const { count, max, times } =
|
|
92
|
-
|
|
97
|
+
retry.count++;
|
|
98
|
+
const { count, max, times } = retry;
|
|
99
|
+
let retryTime;
|
|
100
|
+
if (count - 1 < times.length) {
|
|
101
|
+
retryTime = times[count - 1];
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const lastTime = times[times.length - 1] || 1000;
|
|
105
|
+
const multiplier = Math.min(Math.pow(1.5, count - times.length), 10);
|
|
106
|
+
const jitter = Math.floor(Math.random() * 1000);
|
|
107
|
+
retryTime = Math.min(Math.floor(lastTime * multiplier) + jitter, 30000);
|
|
108
|
+
}
|
|
93
109
|
utils.logger(this.ctx).verbose(`Retry for code ${code} in ${retryTime}ms (${count}/${max})`);
|
|
94
110
|
return retryTime;
|
|
95
111
|
}
|
|
112
|
+
resetRetryCount() {
|
|
113
|
+
for (const retry of Object.values(this.retryCount)) {
|
|
114
|
+
retry.count = 0;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
resetWatchdog() {
|
|
118
|
+
if (this.watchdogTimeout)
|
|
119
|
+
clearTimeout(this.watchdogTimeout);
|
|
120
|
+
this.watchdogTimeout = setTimeout(() => {
|
|
121
|
+
utils.logger(this.ctx).verbose("Watchdog timeout: no packet/pong received from Zalo WebSocket in 50s. Forcing reconnect...");
|
|
122
|
+
if (this.ws) {
|
|
123
|
+
try {
|
|
124
|
+
if (typeof this.ws.terminate === "function") {
|
|
125
|
+
this.ws.terminate();
|
|
126
|
+
}
|
|
127
|
+
else if (typeof this.ws.close === "function") {
|
|
128
|
+
this.ws.close(exports.CloseReason.AbnormalClosure);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (_a) {
|
|
132
|
+
// Ignore if already terminated
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}, 50000);
|
|
136
|
+
}
|
|
96
137
|
shouldRotate(code) {
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
138
|
+
if (code === exports.CloseReason.AbnormalClosure) {
|
|
139
|
+
this.consecutive1006Count++;
|
|
140
|
+
if (this.consecutive1006Count >= 2) {
|
|
141
|
+
this.consecutive1006Count = 0;
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
100
144
|
return false;
|
|
101
|
-
|
|
145
|
+
}
|
|
146
|
+
this.consecutive1006Count = 0;
|
|
147
|
+
return this.ctx.settings.features.socket.rotate_error_codes.includes(code);
|
|
102
148
|
}
|
|
103
149
|
rotateEndpoint() {
|
|
104
|
-
this.rotateCount
|
|
150
|
+
this.rotateCount = (this.rotateCount + 1) % this.urls.length;
|
|
105
151
|
this.wsURL = utils.makeURL(this.ctx, this.urls[this.rotateCount], {
|
|
106
152
|
t: Date.now(),
|
|
107
153
|
});
|
|
@@ -110,37 +156,46 @@ class Listener extends EventEmitter {
|
|
|
110
156
|
start({ retryOnClose = false } = {}) {
|
|
111
157
|
if (this.ws)
|
|
112
158
|
throw new ZaloApiError.ZaloApiError("Already started");
|
|
159
|
+
this.wsURL = utils.makeURL(this.ctx, this.urls[this.rotateCount], {
|
|
160
|
+
t: Date.now(),
|
|
161
|
+
});
|
|
162
|
+
const cookie = this.ctx.cookie.getCookieStringSync("https://chat.zalo.me");
|
|
113
163
|
const ws = new WebSocket(this.wsURL, {
|
|
114
164
|
headers: {
|
|
115
|
-
"accept-
|
|
116
|
-
"accept-language": "en-US,en;q=0.9",
|
|
165
|
+
"accept-language": utils.getBrowserLanguage(this.ctx.language),
|
|
117
166
|
"cache-control": "no-cache",
|
|
118
167
|
connection: "Upgrade",
|
|
119
168
|
host: new URL(this.wsURL).host,
|
|
120
169
|
origin: "https://chat.zalo.me",
|
|
121
|
-
|
|
170
|
+
pragma: "no-cache",
|
|
122
171
|
"sec-websocket-extensions": "permessage-deflate; client_max_window_bits",
|
|
123
172
|
"sec-websocket-version": "13",
|
|
124
173
|
upgrade: "websocket",
|
|
125
174
|
"user-agent": this.userAgent,
|
|
126
|
-
cookie
|
|
175
|
+
cookie,
|
|
127
176
|
},
|
|
128
|
-
agent: this.ctx.options.agent
|
|
177
|
+
agent: this.ctx.options.agent,
|
|
129
178
|
});
|
|
130
179
|
this.ws = ws;
|
|
131
180
|
ws.onopen = () => {
|
|
181
|
+
this.consecutive1006Count = 0;
|
|
182
|
+
this.resetRetryCount();
|
|
183
|
+
this.resetWatchdog();
|
|
132
184
|
this.onConnectedCallback();
|
|
133
185
|
this.emit("connected");
|
|
134
186
|
};
|
|
135
187
|
ws.onclose = (event) => {
|
|
136
188
|
this.reset();
|
|
137
189
|
this.emit("disconnected", event.code, event.reason);
|
|
138
|
-
const retry = retryOnClose &&
|
|
190
|
+
const retry = retryOnClose &&
|
|
191
|
+
this.isRetryable(event.code) &&
|
|
192
|
+
this.canRetry(event.code);
|
|
139
193
|
if (retry && retryOnClose) {
|
|
140
194
|
const shouldRotate = this.shouldRotate(event.code);
|
|
141
195
|
if (shouldRotate) {
|
|
142
196
|
this.rotateEndpoint();
|
|
143
197
|
}
|
|
198
|
+
this.emit("reconnecting", event.code);
|
|
144
199
|
this.retryTimeout = setTimeout(() => {
|
|
145
200
|
this.start({ retryOnClose: true });
|
|
146
201
|
}, retry);
|
|
@@ -155,6 +210,8 @@ class Listener extends EventEmitter {
|
|
|
155
210
|
this.emit("error", event);
|
|
156
211
|
};
|
|
157
212
|
ws.onmessage = async (event) => {
|
|
213
|
+
var _a, _b, _c;
|
|
214
|
+
this.resetWatchdog();
|
|
158
215
|
this.emit("pong"); // Pulse event to notify watchdog that socket is alive
|
|
159
216
|
const { data } = event;
|
|
160
217
|
if (!(data instanceof Buffer))
|
|
@@ -182,9 +239,10 @@ class Listener extends EventEmitter {
|
|
|
182
239
|
this.sendWs(payload, false);
|
|
183
240
|
this.emit("ping");
|
|
184
241
|
};
|
|
242
|
+
const pingIntervalTime = Math.min(Math.max(((_c = (_b = (_a = this.ctx.settings) === null || _a === void 0 ? void 0 : _a.features) === null || _b === void 0 ? void 0 : _b.socket) === null || _c === void 0 ? void 0 : _c.ping_interval) || 25000, 10000), 25000);
|
|
185
243
|
this.pingInterval = setInterval(() => {
|
|
186
244
|
ping();
|
|
187
|
-
},
|
|
245
|
+
}, pingIntervalTime);
|
|
188
246
|
}
|
|
189
247
|
if (version == 1 && cmd == 501 && subCmd == 0) {
|
|
190
248
|
const parsedData = (await utils.decodeEventData(parsed, this.cipherKey)).data;
|
|
@@ -381,6 +439,8 @@ class Listener extends EventEmitter {
|
|
|
381
439
|
stop() {
|
|
382
440
|
if (this.retryTimeout)
|
|
383
441
|
clearTimeout(this.retryTimeout);
|
|
442
|
+
if (this.watchdogTimeout)
|
|
443
|
+
clearTimeout(this.watchdogTimeout);
|
|
384
444
|
if (this.ws) {
|
|
385
445
|
if (this.ws.readyState !== WebSocket.CLOSED) {
|
|
386
446
|
this.ws.close(exports.CloseReason.ManualClosure);
|
|
@@ -437,6 +497,8 @@ class Listener extends EventEmitter {
|
|
|
437
497
|
this.cipherKey = undefined;
|
|
438
498
|
if (this.pingInterval)
|
|
439
499
|
clearInterval(this.pingInterval);
|
|
500
|
+
if (this.watchdogTimeout)
|
|
501
|
+
clearTimeout(this.watchdogTimeout);
|
|
440
502
|
}
|
|
441
503
|
}
|
|
442
504
|
function getHeader(buffer) {
|
package/dist/cjs/apis/login.cjs
CHANGED
|
@@ -6,7 +6,7 @@ var utils = require('../utils.cjs');
|
|
|
6
6
|
async function login(ctx, encryptParams) {
|
|
7
7
|
const encryptedParams = await getEncryptParam(ctx, encryptParams, "getlogininfo");
|
|
8
8
|
try {
|
|
9
|
-
const response = await utils.request(ctx, utils.makeURL(ctx, "https://wpa.chat.zalo.me/api/login/getLoginInfo", Object.assign(Object.assign({}, encryptedParams.params), { nretry: 0 })));
|
|
9
|
+
const response = await utils.request(ctx, utils.makeURL(ctx, "https://wpa.chat.zalo.me/api/login/getLoginInfo", Object.assign(Object.assign({}, encryptedParams.params), { nretry: 0 }), false));
|
|
10
10
|
if (!response.ok)
|
|
11
11
|
throw new ZaloApiError.ZaloApiError("Failed to fetch login info: " + response.statusText);
|
|
12
12
|
const data = await response.json();
|
|
@@ -8,31 +8,16 @@ var ZaloApiLoginQRAborted = require('../Errors/ZaloApiLoginQRAborted.cjs');
|
|
|
8
8
|
var ZaloApiLoginQRDeclined = require('../Errors/ZaloApiLoginQRDeclined.cjs');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Tạo bộ header Sec-CH-UA dùng chung cho toàn bộ các request trong luồng đăng nhập QR.
|
|
11
|
+
* Tạo bộ header Sec-CH-UA và ngôn ngữ dùng chung cho toàn bộ các request trong luồng đăng nhập QR.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* (Browser Mismatch) khi người dùng truyền vào User-Agent của Mac hoặc Linux,
|
|
16
|
-
* khiến hệ thống chống bot của Zalo phát hiện và ban session ngay lập tức.
|
|
13
|
+
* Tất cả giá trị sec-ch-ua-* đều được suy luận ĐỘNG từ ctx.userAgent (chỉ khi là Chromium)
|
|
14
|
+
* và accept-language theo ctx.language, đảm bảo nhất quán 100% với môi trường trình duyệt thật.
|
|
17
15
|
*
|
|
18
|
-
*
|
|
19
|
-
* đảm bảo nhất quán 100% giữa User-Agent và các Client Hint headers.
|
|
20
|
-
*
|
|
21
|
-
* @param ctx - Context chứa ctx.userAgent đã được gán trước khi gọi hàm này
|
|
16
|
+
* @param ctx - Context chứa ctx.userAgent và ctx.language
|
|
22
17
|
* @param extra - Các header bổ sung đặc thù cho từng request (sec-fetch-dest, Referer...)
|
|
23
18
|
*/
|
|
24
19
|
function getLoginHeaders(ctx, extra) {
|
|
25
|
-
|
|
26
|
-
const platform = utils.getPlatformFromUA(ctx.userAgent);
|
|
27
|
-
// Trích xuất phiên bản Chrome major từ UA (ví dụ: "130"), fallback về "130" nếu không phải Chrome
|
|
28
|
-
const chromeVer = utils.getChromeVersionFromUA(ctx.userAgent);
|
|
29
|
-
return Object.assign({ "accept-language": "vi-VN,vi;q=0.9,fr-FR;q=0.8,fr;q=0.7,en-US;q=0.6,en;q=0.5",
|
|
30
|
-
// sec-ch-ua phải khớp với phiên bản Chrome trong User-Agent
|
|
31
|
-
"sec-ch-ua": `"Chromium";v="${chromeVer}", "Google Chrome";v="${chromeVer}", "Not?A_Brand";v="99"`,
|
|
32
|
-
// Luôn là desktop (?0) vì Zalo Web không hỗ trợ mobile
|
|
33
|
-
"sec-ch-ua-mobile": "?0",
|
|
34
|
-
// sec-ch-ua-platform PHẢI nhất quán với User-Agent — đây là nguyên nhân gốc rễ gây ban session
|
|
35
|
-
"sec-ch-ua-platform": `"${platform}"` }, extra);
|
|
20
|
+
return Object.assign(Object.assign({ "accept-language": utils.getBrowserLanguage(ctx.language) }, utils.getSecChUaHeaders(ctx.userAgent)), extra);
|
|
36
21
|
}
|
|
37
22
|
exports.LoginQRCallbackEventType = void 0;
|
|
38
23
|
(function (LoginQRCallbackEventType) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ZaloApiError = require('../Errors/ZaloApiError.cjs');
|
|
4
|
+
var utils = require('../utils.cjs');
|
|
5
|
+
|
|
6
|
+
const preloadConversationsFactory = utils.apiFactory()((api, ctx, utils) => {
|
|
7
|
+
const serviceURL = utils.makeURL(`${api.zpwServiceMap.conversation[0]}/api/preloadconvers/get-last-msgs`);
|
|
8
|
+
/**
|
|
9
|
+
* Preload last messages across all active conversations.
|
|
10
|
+
* Useful for initial state loading and catching up missed messages after reconnect.
|
|
11
|
+
*
|
|
12
|
+
* @throws {ZaloApiError}
|
|
13
|
+
*/
|
|
14
|
+
return async function preloadConversations() {
|
|
15
|
+
const params = {
|
|
16
|
+
imei: ctx.imei,
|
|
17
|
+
};
|
|
18
|
+
const encryptedParams = utils.encodeAES(JSON.stringify(params));
|
|
19
|
+
if (!encryptedParams)
|
|
20
|
+
throw new ZaloApiError.ZaloApiError("Failed to encrypt params");
|
|
21
|
+
const response = await utils.request(utils.makeURL(serviceURL, { params: encryptedParams }), {
|
|
22
|
+
method: "GET",
|
|
23
|
+
});
|
|
24
|
+
return utils.resolve(response);
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
exports.preloadConversationsFactory = preloadConversationsFactory;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ZaloApiError = require('../Errors/ZaloApiError.cjs');
|
|
4
|
+
var utils = require('../utils.cjs');
|
|
5
|
+
|
|
6
|
+
const requestSyncFromPhoneFactory = utils.apiFactory()((api, ctx) => {
|
|
7
|
+
/**
|
|
8
|
+
* Gửi tín hiệu kích hoạt yêu cầu đồng bộ tin nhắn từ xa sang app điện thoại (Trường hợp 2)
|
|
9
|
+
*
|
|
10
|
+
* @throws {ZaloApiError}
|
|
11
|
+
*/
|
|
12
|
+
return async function requestSyncFromPhone() {
|
|
13
|
+
if (!api.listener) {
|
|
14
|
+
throw new ZaloApiError.ZaloApiError("WebSocket listener is not initialized. Please call api.listener.start() first.");
|
|
15
|
+
}
|
|
16
|
+
// Gửi frame yêu cầu đồng bộ qua WebSocket
|
|
17
|
+
const syncPayload = {
|
|
18
|
+
version: 1,
|
|
19
|
+
cmd: 510,
|
|
20
|
+
subCmd: 0,
|
|
21
|
+
data: {
|
|
22
|
+
act: "sync_request",
|
|
23
|
+
imei: ctx.imei,
|
|
24
|
+
ts: Date.now(),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
api.listener.sendWs(syncPayload, true);
|
|
28
|
+
return { success: true };
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
exports.requestSyncFromPhoneFactory = requestSyncFromPhoneFactory;
|
package/dist/cjs/apis.cjs
CHANGED
|
@@ -23,6 +23,7 @@ var createNote = require('./apis/createNote.cjs');
|
|
|
23
23
|
var createPoll = require('./apis/createPoll.cjs');
|
|
24
24
|
var createProductCatalog = require('./apis/createProductCatalog.cjs');
|
|
25
25
|
var createReminder = require('./apis/createReminder.cjs');
|
|
26
|
+
var decryptGids = require('./apis/decryptGids.cjs');
|
|
26
27
|
var deleteAutoReply = require('./apis/deleteAutoReply.cjs');
|
|
27
28
|
var deleteAvatar = require('./apis/deleteAvatar.cjs');
|
|
28
29
|
var deleteCatalog = require('./apis/deleteCatalog.cjs');
|
|
@@ -32,6 +33,7 @@ var deleteMessage = require('./apis/deleteMessage.cjs');
|
|
|
32
33
|
var deleteProductCatalog = require('./apis/deleteProductCatalog.cjs');
|
|
33
34
|
var disableGroupLink = require('./apis/disableGroupLink.cjs');
|
|
34
35
|
var disperseGroup = require('./apis/disperseGroup.cjs');
|
|
36
|
+
var downloadTransferSync = require('./apis/downloadTransferSync.cjs');
|
|
35
37
|
var editNote = require('./apis/editNote.cjs');
|
|
36
38
|
var editReminder = require('./apis/editReminder.cjs');
|
|
37
39
|
var enableGroupLink = require('./apis/enableGroupLink.cjs');
|
|
@@ -96,6 +98,7 @@ var lastOnline = require('./apis/lastOnline.cjs');
|
|
|
96
98
|
var leaveGroup = require('./apis/leaveGroup.cjs');
|
|
97
99
|
var lockPoll = require('./apis/lockPoll.cjs');
|
|
98
100
|
var parseLink = require('./apis/parseLink.cjs');
|
|
101
|
+
var preloadConversations = require('./apis/preloadConversations.cjs');
|
|
99
102
|
var rejectFriendRequest = require('./apis/rejectFriendRequest.cjs');
|
|
100
103
|
var removeFriend = require('./apis/removeFriend.cjs');
|
|
101
104
|
var removeFriendAlias = require('./apis/removeFriendAlias.cjs');
|
|
@@ -105,6 +108,7 @@ var removeQuickMessage = require('./apis/removeQuickMessage.cjs');
|
|
|
105
108
|
var removeReminder = require('./apis/removeReminder.cjs');
|
|
106
109
|
var removeUnreadMark = require('./apis/removeUnreadMark.cjs');
|
|
107
110
|
var removeUserFromGroup = require('./apis/removeUserFromGroup.cjs');
|
|
111
|
+
var requestSyncFromPhone = require('./apis/requestSyncFromPhone.cjs');
|
|
108
112
|
var resetHiddenConversPin = require('./apis/resetHiddenConversPin.cjs');
|
|
109
113
|
var reuseAvatar = require('./apis/reuseAvatar.cjs');
|
|
110
114
|
var reviewPendingMemberRequest = require('./apis/reviewPendingMemberRequest.cjs');
|
|
@@ -174,6 +178,7 @@ class API {
|
|
|
174
178
|
this.createPoll = createPoll.createPollFactory(ctx, this);
|
|
175
179
|
this.createProductCatalog = createProductCatalog.createProductCatalogFactory(ctx, this);
|
|
176
180
|
this.createReminder = createReminder.createReminderFactory(ctx, this);
|
|
181
|
+
this.decryptGids = decryptGids.decryptGidsFactory(ctx, this);
|
|
177
182
|
this.deleteAutoReply = deleteAutoReply.deleteAutoReplyFactory(ctx, this);
|
|
178
183
|
this.deleteAvatar = deleteAvatar.deleteAvatarFactory(ctx, this);
|
|
179
184
|
this.deleteCatalog = deleteCatalog.deleteCatalogFactory(ctx, this);
|
|
@@ -183,6 +188,7 @@ class API {
|
|
|
183
188
|
this.deleteProductCatalog = deleteProductCatalog.deleteProductCatalogFactory(ctx, this);
|
|
184
189
|
this.disableGroupLink = disableGroupLink.disableGroupLinkFactory(ctx, this);
|
|
185
190
|
this.disperseGroup = disperseGroup.disperseGroupFactory(ctx, this);
|
|
191
|
+
this.downloadTransferSync = downloadTransferSync.downloadTransferSyncFactory(ctx, this);
|
|
186
192
|
this.editNote = editNote.editNoteFactory(ctx, this);
|
|
187
193
|
this.editReminder = editReminder.editReminderFactory(ctx, this);
|
|
188
194
|
this.enableGroupLink = enableGroupLink.enableGroupLinkFactory(ctx, this);
|
|
@@ -247,6 +253,7 @@ class API {
|
|
|
247
253
|
this.leaveGroup = leaveGroup.leaveGroupFactory(ctx, this);
|
|
248
254
|
this.lockPoll = lockPoll.lockPollFactory(ctx, this);
|
|
249
255
|
this.parseLink = parseLink.parseLinkFactory(ctx, this);
|
|
256
|
+
this.preloadConversations = preloadConversations.preloadConversationsFactory(ctx, this);
|
|
250
257
|
this.rejectFriendRequest = rejectFriendRequest.rejectFriendRequestFactory(ctx, this);
|
|
251
258
|
this.removeFriend = removeFriend.removeFriendFactory(ctx, this);
|
|
252
259
|
this.removeFriendAlias = removeFriendAlias.removeFriendAliasFactory(ctx, this);
|
|
@@ -256,6 +263,7 @@ class API {
|
|
|
256
263
|
this.removeReminder = removeReminder.removeReminderFactory(ctx, this);
|
|
257
264
|
this.removeUnreadMark = removeUnreadMark.removeUnreadMarkFactory(ctx, this);
|
|
258
265
|
this.removeUserFromGroup = removeUserFromGroup.removeUserFromGroupFactory(ctx, this);
|
|
266
|
+
this.requestSyncFromPhone = requestSyncFromPhone.requestSyncFromPhoneFactory(ctx, this);
|
|
259
267
|
this.resetHiddenConversPin = resetHiddenConversPin.resetHiddenConversPinFactory(ctx, this);
|
|
260
268
|
this.reuseAvatar = reuseAvatar.reuseAvatarFactory(ctx, this);
|
|
261
269
|
this.reviewPendingMemberRequest = reviewPendingMemberRequest.reviewPendingMemberRequestFactory(ctx, this);
|
package/dist/cjs/context.cjs
CHANGED
|
@@ -12,7 +12,7 @@ class CallbacksMap extends Map {
|
|
|
12
12
|
return super.set(key, value);
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
const createContext = (apiType = 30, apiVersion =
|
|
15
|
+
const createContext = (apiType = 30, apiVersion = 689) => ({
|
|
16
16
|
API_TYPE: apiType,
|
|
17
17
|
API_VERSION: apiVersion,
|
|
18
18
|
uploadCallbacks: new CallbacksMap(),
|