@crysnovax/baileys 2.5.0 → 2.5.5
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 +1430 -285
- package/WAProto/index.js +14789 -3578
- package/lib/Defaults/index.js +19 -11
- package/lib/Signal/libsignal.js +42 -18
- package/lib/Socket/Client//342/234/230 +1 -0
- package/lib/Socket/chats.js +246 -91
- package/lib/Socket/messages-recv.js +618 -322
- package/lib/Socket/messages-send.js +174 -74
- package/lib/Socket/newsletter.js +2 -2
- package/lib/Socket/socket.js +12 -20
- package/lib/Socket//342/230/201/357/270/216 +1 -0
- package/lib/Types/Mex.js +39 -0
- package/lib/Types/index.js +1 -0
- package/lib/Types//342/234/206 +1 -0
- package/lib/Utils/index.js +1 -0
- package/lib/Utils/messages.js +148 -114
- package/lib/Utils/use-sqlite-auth-state.js +109 -0
- package/lib/Utils//342/232/211 +1 -0
- package/lib/WABinary/constants.js +99 -5
- package/lib/WABinary//342/216/231 +1 -0
- package/package.json +43 -9
package/lib/Socket/chats.js
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
import NodeCache from '@cacheable/node-cache';
|
|
2
2
|
import { Boom } from '@hapi/boom';
|
|
3
3
|
import { proto } from '../../WAProto/index.js';
|
|
4
|
-
import { DEFAULT_CACHE_TTLS, PROCESSABLE_HISTORY_TYPES } from '../Defaults/index.js';
|
|
4
|
+
import { DEFAULT_CACHE_TTLS, HISTORY_SYNC_PAUSED_TIMEOUT_MS, PROCESSABLE_HISTORY_TYPES } from '../Defaults/index.js';
|
|
5
5
|
import { ALL_WA_PATCH_NAMES } from '../Types/index.js';
|
|
6
6
|
import { SyncState } from '../Types/State.js';
|
|
7
|
-
import { chatModificationToAppPatch, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, extractSyncdPatches, generateProfilePicture, getHistoryMsg, newLTHashState, processSyncAction } from '../Utils/index.js';
|
|
7
|
+
import { chatModificationToAppPatch, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, ensureLTHashStateVersion, extractSyncdPatches, generateProfilePicture, getHistoryMsg, isAppStateSyncIrrecoverable, isMissingKeyError, MAX_SYNC_ATTEMPTS, newLTHashState, processSyncAction } from '../Utils/index.js';
|
|
8
8
|
import { makeMutex } from '../Utils/make-mutex.js';
|
|
9
9
|
import processMessage from '../Utils/process-message.js';
|
|
10
10
|
import { buildTcTokenFromJid } from '../Utils/tc-token-utils.js';
|
|
11
|
-
import { getBinaryNodeChild, getBinaryNodeChildren, isLidUser, isPnUser, jidDecode, jidNormalizedUser, reduceBinaryNodeToDictionary, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
11
|
+
import { getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isLidUser, isPnUser, jidDecode, jidNormalizedUser, reduceBinaryNodeToDictionary, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
12
12
|
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
13
13
|
import { makeSocket } from './socket.js';
|
|
14
|
-
const MAX_SYNC_ATTEMPTS = 2;
|
|
15
|
-
// Lia@Note 08-02-26 --- I know it's not efficient for RSS ಥ‿ಥ
|
|
16
|
-
const USER_ID_CACHE = new Map();
|
|
17
14
|
export const makeChatsSocket = (config) => {
|
|
18
15
|
const { logger, markOnlineOnConnect, fireInitQueries, appStateMacVerification, shouldIgnoreJid, shouldSyncHistoryMessage, getMessage } = config;
|
|
19
16
|
const sock = makeSocket(config);
|
|
20
17
|
const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError, sendUnifiedSession, registerSocketEndHandler } = sock;
|
|
18
|
+
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
21
19
|
let privacySettings;
|
|
20
|
+
/** Server-assigned AB props for protocol behavior. */
|
|
21
|
+
const serverProps = {
|
|
22
|
+
/** AB prop 10518: gate tctoken on 1:1 messages. Default true (safe: avoids 463). */
|
|
23
|
+
privacyTokenOn1to1: true,
|
|
24
|
+
/** AB prop 9666: gate tctoken on profile picture IQs. WA Web default: true. */
|
|
25
|
+
profilePicPrivacyToken: true,
|
|
26
|
+
/** AB prop 14303: issue tctokens to LID instead of PN. WA Web default: false. */
|
|
27
|
+
lidTrustedTokenIssueToLid: false
|
|
28
|
+
};
|
|
22
29
|
let syncState = SyncState.Connecting;
|
|
23
30
|
/** this mutex ensures that messages are processed in order */
|
|
24
31
|
const messageMutex = makeMutex();
|
|
@@ -30,14 +37,20 @@ export const makeChatsSocket = (config) => {
|
|
|
30
37
|
const notificationMutex = makeMutex();
|
|
31
38
|
// Timeout for AwaitingInitialSync state
|
|
32
39
|
let awaitingSyncTimeout;
|
|
40
|
+
// In-memory history sync completion tracking (resets on reconnection)
|
|
41
|
+
const historySyncStatus = {
|
|
42
|
+
initialBootstrapComplete: false,
|
|
43
|
+
recentSyncComplete: false
|
|
44
|
+
};
|
|
45
|
+
let historySyncPausedTimeout;
|
|
46
|
+
// Collections blocked on missing app state sync keys (mirrors WA Web's "Blocked" state).
|
|
47
|
+
// When a key arrives via APP_STATE_SYNC_KEY_SHARE, these are re-synced.
|
|
48
|
+
const blockedCollections = new Set();
|
|
33
49
|
const placeholderResendCache = config.placeholderResendCache ||
|
|
34
50
|
new NodeCache({
|
|
35
51
|
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
36
52
|
useClones: false
|
|
37
53
|
});
|
|
38
|
-
// if (!config.placeholderResendCache) {
|
|
39
|
-
// config.placeholderResendCache = placeholderResendCache;
|
|
40
|
-
// }
|
|
41
54
|
/** helper function to fetch the given app state sync key */
|
|
42
55
|
const getAppStateSyncKey = async (keyId) => {
|
|
43
56
|
const { [keyId]: key } = await authState.keys.get('app-state-sync-key', [keyId]);
|
|
@@ -174,37 +187,24 @@ export const makeChatsSocket = (config) => {
|
|
|
174
187
|
return result.list;
|
|
175
188
|
}
|
|
176
189
|
};
|
|
177
|
-
//
|
|
190
|
+
// crysnovax@Note 06-02-26 --- Quick helper only. This function exists solely for faster lookup with caching.
|
|
178
191
|
const findUserId = async (pnLid) => {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (isPnUser(
|
|
185
|
-
userId.phoneNumber =
|
|
186
|
-
userId.lid = (await signalRepository.lidMapping.getLIDsForPNs([
|
|
187
|
-
if (!userId.lid) {
|
|
188
|
-
userId.lid = 'id-not-found';
|
|
189
|
-
return userId; // Lia@Note 06-02-26 --- Early return to skip caching for non-existent ID
|
|
190
|
-
}
|
|
192
|
+
const normalizedJid = jidNormalizedUser(pnLid);
|
|
193
|
+
const userId = {
|
|
194
|
+
lid: undefined,
|
|
195
|
+
phoneNumber: undefined
|
|
196
|
+
};
|
|
197
|
+
if (isPnUser(normalizedJid) || isHostedPnUser(normalizedJid)) {
|
|
198
|
+
userId.phoneNumber = normalizedJid;
|
|
199
|
+
userId.lid = jidNormalizedUser((await signalRepository.lidMapping.getLIDsForPNs([normalizedJid]))?.[0]?.lid);
|
|
191
200
|
}
|
|
192
|
-
else if (isLidUser(
|
|
193
|
-
userId.lid =
|
|
194
|
-
userId.phoneNumber = (await signalRepository.lidMapping.getPNsForLIDs([
|
|
195
|
-
if (!userId.phoneNumber) {
|
|
196
|
-
userId.phoneNumber = 'id-not-found';
|
|
197
|
-
return userId; // Lia@Note 06-02-26 --- Early return to skip caching for non-existent ID
|
|
198
|
-
}
|
|
201
|
+
else if (isLidUser(normalizedJid) || isHostedLidUser(normalizedJid)) {
|
|
202
|
+
userId.lid = normalizedJid;
|
|
203
|
+
userId.phoneNumber = jidNormalizedUser((await signalRepository.lidMapping.getPNsForLIDs([normalizedJid]))?.[0]?.pn);
|
|
199
204
|
}
|
|
200
205
|
else {
|
|
201
206
|
throw new Boom('Invalid id input to find user ids', { statusCode: 400 });
|
|
202
207
|
}
|
|
203
|
-
userId.phoneNumber = jidNormalizedUser(userId.phoneNumber);
|
|
204
|
-
userId.lid = jidNormalizedUser(userId.lid);
|
|
205
|
-
// Lia@Note 06-02-26 --- I know... it's dirty (╯︵╰,)
|
|
206
|
-
USER_ID_CACHE.set(userId.phoneNumber, userId);
|
|
207
|
-
USER_ID_CACHE.set(userId.lid, userId);
|
|
208
208
|
return userId;
|
|
209
209
|
};
|
|
210
210
|
/** update the profile picture for yourself or a group */
|
|
@@ -293,6 +293,42 @@ export const makeChatsSocket = (config) => {
|
|
|
293
293
|
return getBinaryNodeChildren(listNode, 'item').map(n => n.attrs.jid);
|
|
294
294
|
};
|
|
295
295
|
const updateBlockStatus = async (jid, action) => {
|
|
296
|
+
const normalizedJid = jidNormalizedUser(jid);
|
|
297
|
+
let lid;
|
|
298
|
+
let pn_jid;
|
|
299
|
+
if (isLidUser(normalizedJid) || isHostedLidUser(normalizedJid)) {
|
|
300
|
+
lid = normalizedJid;
|
|
301
|
+
if (action === 'block') {
|
|
302
|
+
const pn = (await findUserId(normalizedJid)).phoneNumber;
|
|
303
|
+
if (!pn) {
|
|
304
|
+
throw new Boom(`Unable to resolve PN JID for LID: ${jid}`, { statusCode: 400 });
|
|
305
|
+
}
|
|
306
|
+
pn_jid = jidNormalizedUser(pn);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
else if (isPnUser(normalizedJid) || isHostedPnUser(normalizedJid)) {
|
|
310
|
+
const mapped = (await findUserId(normalizedJid)).lid;
|
|
311
|
+
if (!mapped) {
|
|
312
|
+
throw new Boom(`Unable to resolve LID for PN JID: ${jid}`, { statusCode: 400 });
|
|
313
|
+
}
|
|
314
|
+
lid = mapped;
|
|
315
|
+
if (action === 'block') {
|
|
316
|
+
pn_jid = jidNormalizedUser(normalizedJid);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
throw new Boom(`Invalid jid: ${jid}`, { statusCode: 400 });
|
|
321
|
+
}
|
|
322
|
+
const itemAttrs = {
|
|
323
|
+
action,
|
|
324
|
+
jid: lid
|
|
325
|
+
};
|
|
326
|
+
if (action === 'block') {
|
|
327
|
+
if (!pn_jid) {
|
|
328
|
+
throw new Boom(`pn_jid required for block: ${jid}`, { statusCode: 400 });
|
|
329
|
+
}
|
|
330
|
+
itemAttrs.pn_jid = pn_jid;
|
|
331
|
+
}
|
|
296
332
|
await query({
|
|
297
333
|
tag: 'iq',
|
|
298
334
|
attrs: {
|
|
@@ -303,10 +339,7 @@ export const makeChatsSocket = (config) => {
|
|
|
303
339
|
content: [
|
|
304
340
|
{
|
|
305
341
|
tag: 'item',
|
|
306
|
-
attrs:
|
|
307
|
-
action,
|
|
308
|
-
jid
|
|
309
|
-
}
|
|
342
|
+
attrs: itemAttrs
|
|
310
343
|
}
|
|
311
344
|
]
|
|
312
345
|
});
|
|
@@ -405,6 +438,9 @@ export const makeChatsSocket = (config) => {
|
|
|
405
438
|
const collectionsToHandle = new Set(collections);
|
|
406
439
|
// in case something goes wrong -- ensure we don't enter a loop that cannot be exited from
|
|
407
440
|
const attemptsMap = {};
|
|
441
|
+
// collections that failed and need a full snapshot on retry
|
|
442
|
+
// mirrors WA Web's ErrorFatal -> force snapshot behavior
|
|
443
|
+
const forceSnapshotCollections = new Set();
|
|
408
444
|
// keep executing till all collections are done
|
|
409
445
|
// sometimes a single patch request will not return all the patches (God knows why)
|
|
410
446
|
// so we fetch till they're all done (this is determined by the "has_more_patches" flag)
|
|
@@ -415,6 +451,7 @@ export const makeChatsSocket = (config) => {
|
|
|
415
451
|
const result = await authState.keys.get('app-state-sync-version', [name]);
|
|
416
452
|
let state = result[name];
|
|
417
453
|
if (state) {
|
|
454
|
+
state = ensureLTHashStateVersion(state);
|
|
418
455
|
if (typeof initialVersionMap[name] === 'undefined') {
|
|
419
456
|
initialVersionMap[name] = state.version;
|
|
420
457
|
}
|
|
@@ -423,14 +460,18 @@ export const makeChatsSocket = (config) => {
|
|
|
423
460
|
state = newLTHashState();
|
|
424
461
|
}
|
|
425
462
|
states[name] = state;
|
|
426
|
-
|
|
463
|
+
const shouldForceSnapshot = forceSnapshotCollections.has(name);
|
|
464
|
+
if (shouldForceSnapshot) {
|
|
465
|
+
forceSnapshotCollections.delete(name);
|
|
466
|
+
}
|
|
467
|
+
logger.info(`resyncing ${name} from v${state.version}${shouldForceSnapshot ? ' (forcing snapshot)' : ''}`);
|
|
427
468
|
nodes.push({
|
|
428
469
|
tag: 'collection',
|
|
429
470
|
attrs: {
|
|
430
471
|
name,
|
|
431
472
|
version: state.version.toString(),
|
|
432
|
-
// return snapshot if
|
|
433
|
-
return_snapshot: (!state.version).toString()
|
|
473
|
+
// return snapshot if syncing from scratch or forcing after a failed attempt
|
|
474
|
+
return_snapshot: (shouldForceSnapshot || !state.version).toString()
|
|
434
475
|
}
|
|
435
476
|
});
|
|
436
477
|
}
|
|
@@ -456,7 +497,7 @@ export const makeChatsSocket = (config) => {
|
|
|
456
497
|
const { patches, hasMorePatches, snapshot } = decoded[name];
|
|
457
498
|
try {
|
|
458
499
|
if (snapshot) {
|
|
459
|
-
const { state: newState, mutationMap } = await decodeSyncdSnapshot(name, snapshot, getCachedAppStateSyncKey, initialVersionMap[name], appStateMacVerification.snapshot);
|
|
500
|
+
const { state: newState, mutationMap } = await decodeSyncdSnapshot(name, snapshot, getCachedAppStateSyncKey, initialVersionMap[name], appStateMacVerification.snapshot, logger);
|
|
460
501
|
states[name] = newState;
|
|
461
502
|
Object.assign(globalMutationMap, mutationMap);
|
|
462
503
|
logger.info(`restored state of ${name} from snapshot to v${newState.version} with mutations`);
|
|
@@ -479,19 +520,37 @@ export const makeChatsSocket = (config) => {
|
|
|
479
520
|
}
|
|
480
521
|
}
|
|
481
522
|
catch (error) {
|
|
482
|
-
// if retry attempts overshoot
|
|
483
|
-
// or key not found
|
|
484
|
-
const isIrrecoverableError = attemptsMap[name] >= MAX_SYNC_ATTEMPTS ||
|
|
485
|
-
error.output?.statusCode === 404 ||
|
|
486
|
-
error.name === 'TypeError';
|
|
487
|
-
logger.info({ name, error: error.stack }, `failed to sync state from version${isIrrecoverableError ? '' : ', removing and trying from scratch'}`);
|
|
488
|
-
await authState.keys.set({ 'app-state-sync-version': { [name]: null } });
|
|
489
|
-
// increment number of retries
|
|
490
523
|
attemptsMap[name] = (attemptsMap[name] || 0) + 1;
|
|
491
|
-
|
|
492
|
-
|
|
524
|
+
const logData = {
|
|
525
|
+
name,
|
|
526
|
+
attempt: attemptsMap[name],
|
|
527
|
+
version: states[name].version,
|
|
528
|
+
statusCode: error.output?.statusCode,
|
|
529
|
+
errorType: error.name,
|
|
530
|
+
error: error.stack
|
|
531
|
+
};
|
|
532
|
+
if (isMissingKeyError(error) && attemptsMap[name] >= MAX_SYNC_ATTEMPTS) {
|
|
533
|
+
// WA Web treats missing keys as "Blocked" — park the collection
|
|
534
|
+
// until the key arrives via APP_STATE_SYNC_KEY_SHARE.
|
|
535
|
+
logger.warn(logData, `${name} blocked on missing key from v${states[name].version}, parking after ${attemptsMap[name]} attempts`);
|
|
536
|
+
blockedCollections.add(name);
|
|
493
537
|
collectionsToHandle.delete(name);
|
|
494
538
|
}
|
|
539
|
+
else if (isMissingKeyError(error)) {
|
|
540
|
+
// Retry with a snapshot which may use a different key.
|
|
541
|
+
logger.info(logData, `${name} blocked on missing key from v${states[name].version}, retrying with snapshot`);
|
|
542
|
+
forceSnapshotCollections.add(name);
|
|
543
|
+
}
|
|
544
|
+
else if (isAppStateSyncIrrecoverable(error, attemptsMap[name])) {
|
|
545
|
+
logger.warn(logData, `failed to sync ${name} from v${states[name].version}, giving up`);
|
|
546
|
+
collectionsToHandle.delete(name);
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
logger.info(logData, `failed to sync ${name} from v${states[name].version}, forcing snapshot retry`);
|
|
550
|
+
// force a full snapshot on retry to recover from
|
|
551
|
+
// corrupted local state (e.g. LTHash MAC mismatch)
|
|
552
|
+
forceSnapshotCollections.add(name);
|
|
553
|
+
}
|
|
495
554
|
}
|
|
496
555
|
}
|
|
497
556
|
}
|
|
@@ -506,25 +565,25 @@ export const makeChatsSocket = (config) => {
|
|
|
506
565
|
* type = "preview" for a low res picture
|
|
507
566
|
* type = "image for the high res picture"
|
|
508
567
|
*/
|
|
509
|
-
const profilePictureUrl = async (jid, type = 'image', timeoutMs) => {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}];
|
|
568
|
+
const profilePictureUrl = async (jid, type = 'image', timeoutMs = 5000, shouldIncludeTcToken = false) => {
|
|
569
|
+
const baseContent = [{ tag: 'picture', attrs: { type, query: 'url' } }];
|
|
570
|
+
// WA Web only includes tctoken for user JIDs (not groups/newsletters)
|
|
571
|
+
// and never for own profile pic (Chat model for self has no tcToken).
|
|
572
|
+
// Including tctoken for own JID causes the server to never respond.
|
|
573
|
+
const normalizedJid = jidNormalizedUser(jid);
|
|
574
|
+
const isUserJid = isPnUser(normalizedJid) || isLidUser(normalizedJid);
|
|
575
|
+
const me = authState.creds.me;
|
|
576
|
+
const isSelf = me && (normalizedJid === jidNormalizedUser(me.id) || (me.lid && normalizedJid === jidNormalizedUser(me.lid)));
|
|
577
|
+
let content = baseContent;
|
|
578
|
+
if (shouldIncludeTcToken && serverProps.profilePicPrivacyToken && isUserJid && !isSelf) {
|
|
579
|
+
content = await buildTcTokenFromJid({
|
|
580
|
+
authState,
|
|
581
|
+
jid: normalizedJid,
|
|
582
|
+
baseContent,
|
|
583
|
+
getLIDForPN
|
|
584
|
+
});
|
|
527
585
|
}
|
|
586
|
+
jid = jidNormalizedUser(jid);
|
|
528
587
|
const result = await query({
|
|
529
588
|
tag: 'iq',
|
|
530
589
|
attrs: {
|
|
@@ -533,16 +592,9 @@ export const makeChatsSocket = (config) => {
|
|
|
533
592
|
type: 'get',
|
|
534
593
|
xmlns: 'w:profile:picture'
|
|
535
594
|
},
|
|
536
|
-
content
|
|
595
|
+
content
|
|
537
596
|
}, timeoutMs);
|
|
538
597
|
const child = getBinaryNodeChild(result, 'picture');
|
|
539
|
-
if (!child) {
|
|
540
|
-
throw new Boom('Picture node missing', { statusCode: 404 });
|
|
541
|
-
}
|
|
542
|
-
const status = child.attrs?.status;
|
|
543
|
-
if (status === '404' || status === '204') {
|
|
544
|
-
throw new Boom('Profile picture not set', { statusCode: 404 });
|
|
545
|
-
}
|
|
546
598
|
return child?.attrs?.url;
|
|
547
599
|
};
|
|
548
600
|
const createCallLink = async (type, event, timeoutMs) => {
|
|
@@ -606,7 +658,12 @@ export const makeChatsSocket = (config) => {
|
|
|
606
658
|
* @param tcToken token for subscription, use if present
|
|
607
659
|
*/
|
|
608
660
|
const presenceSubscribe = async (toJid) => {
|
|
609
|
-
|
|
661
|
+
// Only include tctoken for user JIDs — groups/newsletters don't use tctokens
|
|
662
|
+
const normalizedToJid = jidNormalizedUser(toJid);
|
|
663
|
+
const isUserJid = isPnUser(normalizedToJid) || isLidUser(normalizedToJid);
|
|
664
|
+
const tcTokenContent = isUserJid
|
|
665
|
+
? await buildTcTokenFromJid({ authState, jid: normalizedToJid, getLIDForPN })
|
|
666
|
+
: undefined;
|
|
610
667
|
return sendNode({
|
|
611
668
|
tag: 'presence',
|
|
612
669
|
attrs: {
|
|
@@ -627,7 +684,8 @@ export const makeChatsSocket = (config) => {
|
|
|
627
684
|
if (tag === 'presence') {
|
|
628
685
|
presence = {
|
|
629
686
|
lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available',
|
|
630
|
-
lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined
|
|
687
|
+
lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined,
|
|
688
|
+
groupOnlineCount: attrs.count ? +attrs.count : undefined
|
|
631
689
|
};
|
|
632
690
|
}
|
|
633
691
|
else if (Array.isArray(content)) {
|
|
@@ -661,7 +719,7 @@ export const makeChatsSocket = (config) => {
|
|
|
661
719
|
logger.debug({ patch: patchCreate }, 'applying app patch');
|
|
662
720
|
await resyncAppState([name], false);
|
|
663
721
|
const { [name]: currentSyncVersion } = await authState.keys.get('app-state-sync-version', [name]);
|
|
664
|
-
initial = currentSyncVersion
|
|
722
|
+
initial = currentSyncVersion ? ensureLTHashStateVersion(currentSyncVersion) : newLTHashState();
|
|
665
723
|
encodeResult = await encodeSyncdPatch(patchCreate, myAppStateKeyId, initial, getAppStateSyncKey);
|
|
666
724
|
const { patch, state } = encodeResult;
|
|
667
725
|
const node = {
|
|
@@ -707,22 +765,21 @@ export const makeChatsSocket = (config) => {
|
|
|
707
765
|
}
|
|
708
766
|
}
|
|
709
767
|
};
|
|
710
|
-
/**
|
|
768
|
+
/** fetch AB props */
|
|
711
769
|
const fetchProps = async () => {
|
|
712
|
-
//TODO: implement both protocol 1 and protocol 2 prop fetching, specially for abKey for WM
|
|
713
770
|
const resultNode = await query({
|
|
714
771
|
tag: 'iq',
|
|
715
772
|
attrs: {
|
|
716
773
|
to: S_WHATSAPP_NET,
|
|
717
|
-
xmlns: '
|
|
774
|
+
xmlns: 'abt',
|
|
718
775
|
type: 'get'
|
|
719
776
|
},
|
|
720
777
|
content: [
|
|
721
778
|
{
|
|
722
779
|
tag: 'props',
|
|
723
780
|
attrs: {
|
|
724
|
-
protocol: '
|
|
725
|
-
hash: authState
|
|
781
|
+
protocol: '1',
|
|
782
|
+
...(authState?.creds?.lastPropHash ? { hash: authState.creds.lastPropHash } : {})
|
|
726
783
|
}
|
|
727
784
|
}
|
|
728
785
|
]
|
|
@@ -737,7 +794,20 @@ export const makeChatsSocket = (config) => {
|
|
|
737
794
|
}
|
|
738
795
|
props = reduceBinaryNodeToDictionary(propsNode, 'prop');
|
|
739
796
|
}
|
|
740
|
-
|
|
797
|
+
// Extract protocol-relevant AB props (only the ones we need)
|
|
798
|
+
const privacyTokenProp = props['10518'] ?? props['privacy_token_sending_on_all_1_on_1_messages'];
|
|
799
|
+
if (privacyTokenProp !== undefined) {
|
|
800
|
+
serverProps.privacyTokenOn1to1 = privacyTokenProp === 'true' || privacyTokenProp === '1';
|
|
801
|
+
}
|
|
802
|
+
const profilePicProp = props['9666'] ?? props['profile_scraping_privacy_token_in_photo_iq'];
|
|
803
|
+
if (profilePicProp !== undefined) {
|
|
804
|
+
serverProps.profilePicPrivacyToken = profilePicProp === 'true' || profilePicProp === '1';
|
|
805
|
+
}
|
|
806
|
+
const lidIssueProp = props['14303'] ?? props['lid_trusted_token_issue_to_lid'];
|
|
807
|
+
if (lidIssueProp !== undefined) {
|
|
808
|
+
serverProps.lidTrustedTokenIssueToLid = lidIssueProp === 'true' || lidIssueProp === '1';
|
|
809
|
+
}
|
|
810
|
+
logger.debug({ serverProps }, 'fetched props');
|
|
741
811
|
return props;
|
|
742
812
|
};
|
|
743
813
|
/**
|
|
@@ -877,6 +947,47 @@ export const makeChatsSocket = (config) => {
|
|
|
877
947
|
? shouldSyncHistoryMessage(historyMsg) &&
|
|
878
948
|
PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
|
|
879
949
|
: false;
|
|
950
|
+
if (historyMsg && shouldProcessHistoryMsg) {
|
|
951
|
+
const syncType = historyMsg.syncType;
|
|
952
|
+
// INITIAL_BOOTSTRAP — fire immediately, no progress check (same as WA Web K function)
|
|
953
|
+
if (syncType === proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP &&
|
|
954
|
+
!historySyncStatus.initialBootstrapComplete) {
|
|
955
|
+
historySyncStatus.initialBootstrapComplete = true;
|
|
956
|
+
ev.emit('messaging-history.status', {
|
|
957
|
+
syncType,
|
|
958
|
+
status: 'complete',
|
|
959
|
+
explicit: true
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
// RECENT with progress === 100 — explicit completion
|
|
963
|
+
if (syncType === proto.HistorySync.HistorySyncType.RECENT &&
|
|
964
|
+
historyMsg.progress === 100 &&
|
|
965
|
+
!historySyncStatus.recentSyncComplete) {
|
|
966
|
+
historySyncStatus.recentSyncComplete = true;
|
|
967
|
+
clearTimeout(historySyncPausedTimeout);
|
|
968
|
+
historySyncPausedTimeout = undefined;
|
|
969
|
+
ev.emit('messaging-history.status', {
|
|
970
|
+
syncType,
|
|
971
|
+
status: 'complete',
|
|
972
|
+
explicit: true
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
// Reset 120s paused timeout on any RECENT chunk (like WA Web's handleChunkProgress)
|
|
976
|
+
if (syncType === proto.HistorySync.HistorySyncType.RECENT && !historySyncStatus.recentSyncComplete) {
|
|
977
|
+
clearTimeout(historySyncPausedTimeout);
|
|
978
|
+
historySyncPausedTimeout = setTimeout(() => {
|
|
979
|
+
if (!historySyncStatus.recentSyncComplete) {
|
|
980
|
+
historySyncStatus.recentSyncComplete = true;
|
|
981
|
+
ev.emit('messaging-history.status', {
|
|
982
|
+
syncType: proto.HistorySync.HistorySyncType.RECENT,
|
|
983
|
+
status: 'paused',
|
|
984
|
+
explicit: false
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
historySyncPausedTimeout = undefined;
|
|
988
|
+
}, HISTORY_SYNC_PAUSED_TIMEOUT_MS);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
880
991
|
// State machine: decide on sync and flush
|
|
881
992
|
if (historyMsg && syncState === SyncState.AwaitingInitialSync) {
|
|
882
993
|
if (awaitingSyncTimeout) {
|
|
@@ -896,6 +1007,8 @@ export const makeChatsSocket = (config) => {
|
|
|
896
1007
|
}
|
|
897
1008
|
const doAppStateSync = async () => {
|
|
898
1009
|
if (syncState === SyncState.Syncing) {
|
|
1010
|
+
// All collections will be synced, so clear any blocked ones
|
|
1011
|
+
blockedCollections.clear();
|
|
899
1012
|
logger.info('Doing app state sync');
|
|
900
1013
|
await resyncAppState(ALL_WA_PATCH_NAMES, true);
|
|
901
1014
|
// Sync is complete, go online and flush everything
|
|
@@ -955,6 +1068,11 @@ export const makeChatsSocket = (config) => {
|
|
|
955
1068
|
}
|
|
956
1069
|
});
|
|
957
1070
|
ev.on('connection.update', ({ connection, receivedPendingNotifications }) => {
|
|
1071
|
+
if (connection === 'close') {
|
|
1072
|
+
blockedCollections.clear();
|
|
1073
|
+
clearTimeout(historySyncPausedTimeout);
|
|
1074
|
+
historySyncPausedTimeout = undefined;
|
|
1075
|
+
}
|
|
958
1076
|
if (connection === 'open') {
|
|
959
1077
|
if (fireInitQueries) {
|
|
960
1078
|
executeInitQueries().catch(error => onUnexpectedError(error, 'init queries'));
|
|
@@ -964,6 +1082,10 @@ export const makeChatsSocket = (config) => {
|
|
|
964
1082
|
if (!receivedPendingNotifications || syncState !== SyncState.Connecting) {
|
|
965
1083
|
return;
|
|
966
1084
|
}
|
|
1085
|
+
historySyncStatus.initialBootstrapComplete = false;
|
|
1086
|
+
historySyncStatus.recentSyncComplete = false;
|
|
1087
|
+
clearTimeout(historySyncPausedTimeout);
|
|
1088
|
+
historySyncPausedTimeout = undefined;
|
|
967
1089
|
syncState = SyncState.AwaitingInitialSync;
|
|
968
1090
|
logger.info('Connection is now AwaitingInitialSync, buffering events');
|
|
969
1091
|
ev.buffer();
|
|
@@ -976,19 +1098,49 @@ export const makeChatsSocket = (config) => {
|
|
|
976
1098
|
setTimeout(() => ev.flush(), 0);
|
|
977
1099
|
return;
|
|
978
1100
|
}
|
|
979
|
-
|
|
1101
|
+
// On reconnection (accountSyncCounter > 0), the server does not push
|
|
1102
|
+
// history sync notifications — the device already has its data.
|
|
1103
|
+
// Skip the 20s wait and go online immediately.
|
|
1104
|
+
if (authState.creds.accountSyncCounter > 0) {
|
|
1105
|
+
logger.info('Reconnection with existing sync data, skipping history sync wait. Transitioning to Online.');
|
|
1106
|
+
syncState = SyncState.Online;
|
|
1107
|
+
setTimeout(() => ev.flush(), 0);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
logger.info('First connection, awaiting history sync notification with a 20s timeout.');
|
|
980
1111
|
if (awaitingSyncTimeout) {
|
|
981
1112
|
clearTimeout(awaitingSyncTimeout);
|
|
982
1113
|
}
|
|
983
1114
|
awaitingSyncTimeout = setTimeout(() => {
|
|
984
1115
|
if (syncState === SyncState.AwaitingInitialSync) {
|
|
985
|
-
// TODO: investigate
|
|
986
1116
|
logger.warn('Timeout in AwaitingInitialSync, forcing state to Online and flushing buffer');
|
|
987
1117
|
syncState = SyncState.Online;
|
|
988
1118
|
ev.flush();
|
|
1119
|
+
// Increment so subsequent reconnections skip the 20s wait.
|
|
1120
|
+
// Late-arriving history is still processed via processMessage
|
|
1121
|
+
// regardless of the state machine phase.
|
|
1122
|
+
const accountSyncCounter = (authState.creds.accountSyncCounter || 0) + 1;
|
|
1123
|
+
ev.emit('creds.update', { accountSyncCounter });
|
|
989
1124
|
}
|
|
990
1125
|
}, 20000);
|
|
991
1126
|
});
|
|
1127
|
+
// When an app state sync key arrives (myAppStateKeyId is set) and there are
|
|
1128
|
+
// collections blocked on a missing key, trigger a re-sync for just those collections.
|
|
1129
|
+
// This mirrors WA Web's Blocked → retry-on-key-arrival behavior.
|
|
1130
|
+
ev.on('creds.update', ({ myAppStateKeyId }) => {
|
|
1131
|
+
if (!myAppStateKeyId || blockedCollections.size === 0) {
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
// If we're in the middle of a full sync, doAppStateSync handles all collections
|
|
1135
|
+
if (syncState === SyncState.Syncing) {
|
|
1136
|
+
blockedCollections.clear();
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const collections = [...blockedCollections];
|
|
1140
|
+
blockedCollections.clear();
|
|
1141
|
+
logger.info({ collections }, 'app state sync key arrived, re-syncing blocked collections');
|
|
1142
|
+
resyncAppState(collections, false).catch(error => onUnexpectedError(error, 'blocked collections resync'));
|
|
1143
|
+
});
|
|
992
1144
|
ev.on('lid-mapping.update', async ({ lid, pn }) => {
|
|
993
1145
|
try {
|
|
994
1146
|
await signalRepository.lidMapping.storeLIDPNMappings([{ lid, pn }]);
|
|
@@ -1010,6 +1162,8 @@ export const makeChatsSocket = (config) => {
|
|
|
1010
1162
|
});
|
|
1011
1163
|
return {
|
|
1012
1164
|
...sock,
|
|
1165
|
+
findUserId,
|
|
1166
|
+
serverProps,
|
|
1013
1167
|
createCallLink,
|
|
1014
1168
|
getBotListV2,
|
|
1015
1169
|
messageMutex,
|
|
@@ -1025,7 +1179,6 @@ export const makeChatsSocket = (config) => {
|
|
|
1025
1179
|
fetchBlocklist,
|
|
1026
1180
|
fetchStatus,
|
|
1027
1181
|
fetchDisappearingDuration,
|
|
1028
|
-
findUserId,
|
|
1029
1182
|
updateProfilePicture,
|
|
1030
1183
|
removeProfilePicture,
|
|
1031
1184
|
updateProfileStatus,
|
|
@@ -1047,6 +1200,7 @@ export const makeChatsSocket = (config) => {
|
|
|
1047
1200
|
cleanDirtyBits,
|
|
1048
1201
|
addOrEditContact,
|
|
1049
1202
|
removeContact,
|
|
1203
|
+
placeholderResendCache,
|
|
1050
1204
|
addLabel,
|
|
1051
1205
|
addChatLabel,
|
|
1052
1206
|
removeChatLabel,
|
|
@@ -1056,4 +1210,5 @@ export const makeChatsSocket = (config) => {
|
|
|
1056
1210
|
addOrEditQuickReply,
|
|
1057
1211
|
removeQuickReply
|
|
1058
1212
|
};
|
|
1059
|
-
};
|
|
1213
|
+
};
|
|
1214
|
+
//# sourceMappingURL=chats.js.map
|