baileys-joss 1.0.2 → 2.0.0
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 +1052 -2593
- package/WAProto/fix-imports.js +22 -18
- package/WAProto/index.js +22 -18
- package/lib/Defaults/index.d.ts +15 -6
- package/lib/Defaults/index.d.ts.map +1 -1
- package/lib/Defaults/index.js +16 -7
- package/lib/Defaults/index.js.map +1 -1
- package/lib/Signal/libsignal.d.ts.map +1 -1
- package/lib/Signal/libsignal.js +105 -16
- package/lib/Signal/libsignal.js.map +1 -1
- package/lib/Signal/lid-mapping.d.ts +8 -9
- package/lib/Signal/lid-mapping.d.ts.map +1 -1
- package/lib/Signal/lid-mapping.js +88 -31
- package/lib/Signal/lid-mapping.js.map +1 -1
- package/lib/Socket/business.d.ts +21 -4
- package/lib/Socket/business.d.ts.map +1 -1
- package/lib/Socket/chats.d.ts +18 -2
- package/lib/Socket/chats.d.ts.map +1 -1
- package/lib/Socket/chats.js +263 -43
- package/lib/Socket/chats.js.map +1 -1
- package/lib/Socket/communities.d.ts +21 -4
- package/lib/Socket/communities.d.ts.map +1 -1
- package/lib/Socket/groups.d.ts +15 -2
- package/lib/Socket/groups.d.ts.map +1 -1
- package/lib/Socket/groups.js +20 -0
- package/lib/Socket/groups.js.map +1 -1
- package/lib/Socket/index.d.ts +21 -4
- package/lib/Socket/index.d.ts.map +1 -1
- package/lib/Socket/messages-recv.d.ts +25 -7
- package/lib/Socket/messages-recv.d.ts.map +1 -1
- package/lib/Socket/messages-recv.js +810 -332
- package/lib/Socket/messages-recv.js.map +1 -1
- package/lib/Socket/messages-send.d.ts +20 -2
- package/lib/Socket/messages-send.d.ts.map +1 -1
- package/lib/Socket/messages-send.js +149 -47
- package/lib/Socket/messages-send.js.map +1 -1
- package/lib/Socket/newsletter.d.ts +13 -1
- package/lib/Socket/newsletter.d.ts.map +1 -1
- package/lib/Socket/newsletter.js +2 -2
- package/lib/Socket/newsletter.js.map +1 -1
- package/lib/Socket/socket.d.ts +8 -2
- package/lib/Socket/socket.d.ts.map +1 -1
- package/lib/Socket/socket.js +109 -26
- package/lib/Socket/socket.js.map +1 -1
- package/lib/Types/Auth.d.ts +2 -0
- package/lib/Types/Auth.d.ts.map +1 -1
- package/lib/Types/Call.d.ts +2 -1
- package/lib/Types/Call.d.ts.map +1 -1
- package/lib/Types/Chat.d.ts +1 -0
- package/lib/Types/Chat.d.ts.map +1 -1
- package/lib/Types/Contact.d.ts +2 -0
- package/lib/Types/Contact.d.ts.map +1 -1
- package/lib/Types/Events.d.ts +21 -1
- package/lib/Types/Events.d.ts.map +1 -1
- package/lib/Types/GroupMetadata.d.ts +4 -0
- package/lib/Types/GroupMetadata.d.ts.map +1 -1
- package/lib/Types/Message.d.ts +20 -5
- package/lib/Types/Message.d.ts.map +1 -1
- package/lib/Types/Message.js.map +1 -1
- package/lib/Types/{Newsletter.d.ts → Mex.d.ts} +11 -5
- package/lib/Types/Mex.d.ts.map +1 -0
- package/lib/Types/{Newsletter.js → Mex.js} +9 -3
- package/lib/Types/Mex.js.map +1 -0
- package/lib/Types/Signal.d.ts +12 -1
- package/lib/Types/Signal.d.ts.map +1 -1
- package/lib/Types/Socket.d.ts +3 -0
- package/lib/Types/Socket.d.ts.map +1 -1
- package/lib/Types/State.d.ts +58 -0
- package/lib/Types/State.d.ts.map +1 -1
- package/lib/Types/State.js +43 -0
- package/lib/Types/State.js.map +1 -1
- package/lib/Types/index.d.ts +2 -1
- package/lib/Types/index.d.ts.map +1 -1
- package/lib/Types/index.js +1 -1
- package/lib/Types/index.js.map +1 -1
- package/lib/Utils/auth-utils.d.ts +5 -0
- package/lib/Utils/auth-utils.d.ts.map +1 -1
- package/lib/Utils/auth-utils.js +12 -0
- package/lib/Utils/auth-utils.js.map +1 -1
- package/lib/Utils/browser-utils.d.ts.map +1 -1
- package/lib/Utils/browser-utils.js +1 -0
- package/lib/Utils/browser-utils.js.map +1 -1
- package/lib/Utils/chat-export.js +30 -30
- package/lib/Utils/chat-utils.d.ts +31 -1
- package/lib/Utils/chat-utils.d.ts.map +1 -1
- package/lib/Utils/chat-utils.js +123 -63
- package/lib/Utils/chat-utils.js.map +1 -1
- package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
- package/lib/Utils/companion-reg-client-utils.d.ts.map +1 -0
- package/lib/Utils/companion-reg-client-utils.js +35 -0
- package/lib/Utils/companion-reg-client-utils.js.map +1 -0
- package/lib/Utils/crypto.d.ts +4 -8
- package/lib/Utils/crypto.d.ts.map +1 -1
- package/lib/Utils/crypto.js +2 -26
- package/lib/Utils/crypto.js.map +1 -1
- package/lib/Utils/decode-wa-message.d.ts +18 -0
- package/lib/Utils/decode-wa-message.d.ts.map +1 -1
- package/lib/Utils/decode-wa-message.js +34 -0
- package/lib/Utils/decode-wa-message.js.map +1 -1
- package/lib/Utils/event-buffer.d.ts +2 -0
- package/lib/Utils/event-buffer.d.ts.map +1 -1
- package/lib/Utils/event-buffer.js +49 -1
- package/lib/Utils/event-buffer.js.map +1 -1
- package/lib/Utils/generics.d.ts +1 -0
- package/lib/Utils/generics.d.ts.map +1 -1
- package/lib/Utils/generics.js +10 -1
- package/lib/Utils/generics.js.map +1 -1
- package/lib/Utils/history.d.ts +2 -0
- package/lib/Utils/history.d.ts.map +1 -1
- package/lib/Utils/history.js +12 -9
- package/lib/Utils/history.js.map +1 -1
- package/lib/Utils/identity-change-handler.d.ts +7 -0
- package/lib/Utils/identity-change-handler.d.ts.map +1 -1
- package/lib/Utils/identity-change-handler.js +1 -0
- package/lib/Utils/identity-change-handler.js.map +1 -1
- package/lib/Utils/index.d.ts +27 -48
- package/lib/Utils/index.d.ts.map +1 -1
- package/lib/Utils/index.js +27 -77
- package/lib/Utils/index.js.map +1 -1
- package/lib/Utils/link-preview.js +2 -2
- package/lib/Utils/link-preview.js.map +1 -1
- package/lib/Utils/lt-hash.d.ts +7 -12
- package/lib/Utils/lt-hash.d.ts.map +1 -1
- package/lib/Utils/lt-hash.js +2 -42
- package/lib/Utils/lt-hash.js.map +1 -1
- package/lib/Utils/meme-generator.js +33 -33
- package/lib/Utils/message-retry-manager.d.ts +35 -2
- package/lib/Utils/message-retry-manager.d.ts.map +1 -1
- package/lib/Utils/message-retry-manager.js +99 -2
- package/lib/Utils/message-retry-manager.js.map +1 -1
- package/lib/Utils/messages-media.d.ts +6 -3
- package/lib/Utils/messages-media.d.ts.map +1 -1
- package/lib/Utils/messages-media.js +25 -11
- package/lib/Utils/messages-media.js.map +1 -1
- package/lib/Utils/messages.d.ts.map +1 -1
- package/lib/Utils/messages.js +28 -5
- package/lib/Utils/messages.js.map +1 -1
- package/lib/Utils/mini-games.js +7 -7
- package/lib/Utils/noise-handler.d.ts +7 -7
- package/lib/Utils/noise-handler.d.ts.map +1 -1
- package/lib/Utils/noise-handler.js +33 -27
- package/lib/Utils/noise-handler.js.map +1 -1
- package/lib/Utils/offline-node-processor.d.ts +17 -0
- package/lib/Utils/offline-node-processor.d.ts.map +1 -0
- package/lib/Utils/offline-node-processor.js +40 -0
- package/lib/Utils/offline-node-processor.js.map +1 -0
- package/lib/Utils/process-message.d.ts.map +1 -1
- package/lib/Utils/process-message.js +140 -14
- package/lib/Utils/process-message.js.map +1 -1
- package/lib/Utils/qr-generator.js +16 -16
- package/lib/Utils/reporting-utils.js +2 -2
- package/lib/Utils/reporting-utils.js.map +1 -1
- package/lib/Utils/signal.d.ts +13 -0
- package/lib/Utils/signal.d.ts.map +1 -1
- package/lib/Utils/signal.js +42 -0
- package/lib/Utils/signal.js.map +1 -1
- package/lib/Utils/stanza-ack.d.ts +11 -0
- package/lib/Utils/stanza-ack.d.ts.map +1 -0
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/stanza-ack.js.map +1 -0
- package/lib/Utils/sync-action-utils.d.ts.map +1 -1
- package/lib/Utils/sync-action-utils.js +1 -0
- package/lib/Utils/sync-action-utils.js.map +1 -1
- package/lib/Utils/tc-token-utils.d.ts +26 -1
- package/lib/Utils/tc-token-utils.d.ts.map +1 -1
- package/lib/Utils/tc-token-utils.js +151 -5
- package/lib/Utils/tc-token-utils.js.map +1 -1
- package/lib/Utils/templates.js +104 -104
- package/lib/Utils/validate-connection.d.ts.map +1 -1
- package/lib/Utils/validate-connection.js +12 -2
- package/lib/Utils/validate-connection.js.map +1 -1
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +1 -1
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +26 -3
- package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +1 -1
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/index.d.ts +1 -0
- package/lib/WAUSync/Protocols/index.d.ts.map +1 -1
- package/lib/WAUSync/Protocols/index.js +1 -0
- package/lib/WAUSync/Protocols/index.js.map +1 -1
- package/lib/WAUSync/USyncQuery.d.ts +1 -0
- package/lib/WAUSync/USyncQuery.d.ts.map +1 -1
- package/lib/WAUSync/USyncQuery.js +6 -2
- package/lib/WAUSync/USyncQuery.js.map +1 -1
- package/lib/WAUSync/USyncUser.d.ts +4 -0
- package/lib/WAUSync/USyncUser.d.ts.map +1 -1
- package/lib/WAUSync/USyncUser.js +8 -0
- package/lib/WAUSync/USyncUser.js.map +1 -1
- package/package.json +41 -12
- package/lib/Types/Newsletter.d.ts.map +0 -1
- package/lib/Types/Newsletter.js.map +0 -1
package/lib/Socket/chats.js
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
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, generatePanoramaProfilePicture, getHistoryMsg, newLTHashState, processSyncAction } from '../Utils/index.js';
|
|
7
|
+
import { chatModificationToAppPatch, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, ensureLTHashStateVersion, extractSyncdPatches, generateProfilePicture, generatePanoramaProfilePicture, 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, 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
|
|
14
|
+
export const buildProfilePictureQueryContent = (type, tcTokenContent) => {
|
|
15
|
+
const picture = { tag: 'picture', attrs: { type, query: 'url' } };
|
|
16
|
+
if (tcTokenContent?.length) {
|
|
17
|
+
picture.content = tcTokenContent;
|
|
18
|
+
}
|
|
19
|
+
return [picture];
|
|
20
|
+
};
|
|
15
21
|
export const makeChatsSocket = (config) => {
|
|
16
22
|
const { logger, markOnlineOnConnect, fireInitQueries, appStateMacVerification, shouldIgnoreJid, shouldSyncHistoryMessage, getMessage } = config;
|
|
17
23
|
const sock = makeSocket(config);
|
|
18
|
-
const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError } = sock;
|
|
24
|
+
const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError, sendUnifiedSession, registerSocketEndHandler } = sock;
|
|
25
|
+
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
19
26
|
let privacySettings;
|
|
27
|
+
/** Server-assigned AB props for protocol behavior. */
|
|
28
|
+
const serverProps = {
|
|
29
|
+
/** AB prop 10518: gate tctoken on 1:1 messages. Default true (safe: avoids 463). */
|
|
30
|
+
privacyTokenOn1to1: true,
|
|
31
|
+
/** AB prop 9666: gate tctoken on profile picture IQs. WA Web default: true. */
|
|
32
|
+
profilePicPrivacyToken: true,
|
|
33
|
+
/** AB prop 14303: issue tctokens to LID instead of PN. WA Web default: false. */
|
|
34
|
+
lidTrustedTokenIssueToLid: false
|
|
35
|
+
};
|
|
20
36
|
let syncState = SyncState.Connecting;
|
|
21
37
|
/** this mutex ensures that messages are processed in order */
|
|
22
38
|
const messageMutex = makeMutex();
|
|
@@ -28,14 +44,20 @@ export const makeChatsSocket = (config) => {
|
|
|
28
44
|
const notificationMutex = makeMutex();
|
|
29
45
|
// Timeout for AwaitingInitialSync state
|
|
30
46
|
let awaitingSyncTimeout;
|
|
47
|
+
// In-memory history sync completion tracking (resets on reconnection)
|
|
48
|
+
const historySyncStatus = {
|
|
49
|
+
initialBootstrapComplete: false,
|
|
50
|
+
recentSyncComplete: false
|
|
51
|
+
};
|
|
52
|
+
let historySyncPausedTimeout;
|
|
53
|
+
// Collections blocked on missing app state sync keys (mirrors WA Web's "Blocked" state).
|
|
54
|
+
// When a key arrives via APP_STATE_SYNC_KEY_SHARE, these are re-synced.
|
|
55
|
+
const blockedCollections = new Set();
|
|
31
56
|
const placeholderResendCache = config.placeholderResendCache ||
|
|
32
57
|
new NodeCache({
|
|
33
58
|
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
34
59
|
useClones: false
|
|
35
60
|
});
|
|
36
|
-
if (!config.placeholderResendCache) {
|
|
37
|
-
config.placeholderResendCache = placeholderResendCache;
|
|
38
|
-
}
|
|
39
61
|
/** helper function to fetch the given app state sync key */
|
|
40
62
|
const getAppStateSyncKey = async (keyId) => {
|
|
41
63
|
const { [keyId]: key } = await authState.keys.get('app-state-sync-key', [keyId]);
|
|
@@ -300,6 +322,42 @@ export const makeChatsSocket = (config) => {
|
|
|
300
322
|
return getBinaryNodeChildren(listNode, 'item').map(n => n.attrs.jid);
|
|
301
323
|
};
|
|
302
324
|
const updateBlockStatus = async (jid, action) => {
|
|
325
|
+
const normalizedJid = jidNormalizedUser(jid);
|
|
326
|
+
let lid;
|
|
327
|
+
let pn_jid;
|
|
328
|
+
if (isLidUser(normalizedJid) || isHostedLidUser(normalizedJid)) {
|
|
329
|
+
lid = normalizedJid;
|
|
330
|
+
if (action === 'block') {
|
|
331
|
+
const pn = await signalRepository.lidMapping.getPNForLID(normalizedJid);
|
|
332
|
+
if (!pn) {
|
|
333
|
+
throw new Boom(`Unable to resolve PN JID for LID: ${jid}`, { statusCode: 400 });
|
|
334
|
+
}
|
|
335
|
+
pn_jid = jidNormalizedUser(pn);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else if (isPnUser(normalizedJid) || isHostedPnUser(normalizedJid)) {
|
|
339
|
+
const mapped = await signalRepository.lidMapping.getLIDForPN(normalizedJid);
|
|
340
|
+
if (!mapped) {
|
|
341
|
+
throw new Boom(`Unable to resolve LID for PN JID: ${jid}`, { statusCode: 400 });
|
|
342
|
+
}
|
|
343
|
+
lid = mapped;
|
|
344
|
+
if (action === 'block') {
|
|
345
|
+
pn_jid = jidNormalizedUser(normalizedJid);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
throw new Boom(`Invalid jid: ${jid}`, { statusCode: 400 });
|
|
350
|
+
}
|
|
351
|
+
const itemAttrs = {
|
|
352
|
+
action,
|
|
353
|
+
jid: lid
|
|
354
|
+
};
|
|
355
|
+
if (action === 'block') {
|
|
356
|
+
if (!pn_jid) {
|
|
357
|
+
throw new Boom(`pn_jid required for block: ${jid}`, { statusCode: 400 });
|
|
358
|
+
}
|
|
359
|
+
itemAttrs.pn_jid = pn_jid;
|
|
360
|
+
}
|
|
303
361
|
await query({
|
|
304
362
|
tag: 'iq',
|
|
305
363
|
attrs: {
|
|
@@ -310,10 +368,7 @@ export const makeChatsSocket = (config) => {
|
|
|
310
368
|
content: [
|
|
311
369
|
{
|
|
312
370
|
tag: 'item',
|
|
313
|
-
attrs:
|
|
314
|
-
action,
|
|
315
|
-
jid
|
|
316
|
-
}
|
|
371
|
+
attrs: itemAttrs
|
|
317
372
|
}
|
|
318
373
|
]
|
|
319
374
|
});
|
|
@@ -395,6 +450,15 @@ export const makeChatsSocket = (config) => {
|
|
|
395
450
|
};
|
|
396
451
|
};
|
|
397
452
|
const resyncAppState = ev.createBufferedFunction(async (collections, isInitialSync) => {
|
|
453
|
+
const appStateSyncKeyCache = new Map();
|
|
454
|
+
const getCachedAppStateSyncKey = async (keyId) => {
|
|
455
|
+
if (appStateSyncKeyCache.has(keyId)) {
|
|
456
|
+
return appStateSyncKeyCache.get(keyId) ?? undefined;
|
|
457
|
+
}
|
|
458
|
+
const key = await getAppStateSyncKey(keyId);
|
|
459
|
+
appStateSyncKeyCache.set(keyId, key ?? null);
|
|
460
|
+
return key;
|
|
461
|
+
};
|
|
398
462
|
// we use this to determine which events to fire
|
|
399
463
|
// otherwise when we resync from scratch -- all notifications will fire
|
|
400
464
|
const initialVersionMap = {};
|
|
@@ -403,6 +467,9 @@ export const makeChatsSocket = (config) => {
|
|
|
403
467
|
const collectionsToHandle = new Set(collections);
|
|
404
468
|
// in case something goes wrong -- ensure we don't enter a loop that cannot be exited from
|
|
405
469
|
const attemptsMap = {};
|
|
470
|
+
// collections that failed and need a full snapshot on retry
|
|
471
|
+
// mirrors WA Web's ErrorFatal -> force snapshot behavior
|
|
472
|
+
const forceSnapshotCollections = new Set();
|
|
406
473
|
// keep executing till all collections are done
|
|
407
474
|
// sometimes a single patch request will not return all the patches (God knows why)
|
|
408
475
|
// so we fetch till they're all done (this is determined by the "has_more_patches" flag)
|
|
@@ -413,6 +480,7 @@ export const makeChatsSocket = (config) => {
|
|
|
413
480
|
const result = await authState.keys.get('app-state-sync-version', [name]);
|
|
414
481
|
let state = result[name];
|
|
415
482
|
if (state) {
|
|
483
|
+
state = ensureLTHashStateVersion(state);
|
|
416
484
|
if (typeof initialVersionMap[name] === 'undefined') {
|
|
417
485
|
initialVersionMap[name] = state.version;
|
|
418
486
|
}
|
|
@@ -421,14 +489,18 @@ export const makeChatsSocket = (config) => {
|
|
|
421
489
|
state = newLTHashState();
|
|
422
490
|
}
|
|
423
491
|
states[name] = state;
|
|
424
|
-
|
|
492
|
+
const shouldForceSnapshot = forceSnapshotCollections.has(name);
|
|
493
|
+
if (shouldForceSnapshot) {
|
|
494
|
+
forceSnapshotCollections.delete(name);
|
|
495
|
+
}
|
|
496
|
+
logger.info(`resyncing ${name} from v${state.version}${shouldForceSnapshot ? ' (forcing snapshot)' : ''}`);
|
|
425
497
|
nodes.push({
|
|
426
498
|
tag: 'collection',
|
|
427
499
|
attrs: {
|
|
428
500
|
name,
|
|
429
501
|
version: state.version.toString(),
|
|
430
|
-
// return snapshot if
|
|
431
|
-
return_snapshot: (!state.version).toString()
|
|
502
|
+
// return snapshot if syncing from scratch or forcing after a failed attempt
|
|
503
|
+
return_snapshot: (shouldForceSnapshot || !state.version).toString()
|
|
432
504
|
}
|
|
433
505
|
});
|
|
434
506
|
}
|
|
@@ -454,7 +526,7 @@ export const makeChatsSocket = (config) => {
|
|
|
454
526
|
const { patches, hasMorePatches, snapshot } = decoded[name];
|
|
455
527
|
try {
|
|
456
528
|
if (snapshot) {
|
|
457
|
-
const { state: newState, mutationMap } = await decodeSyncdSnapshot(name, snapshot,
|
|
529
|
+
const { state: newState, mutationMap } = await decodeSyncdSnapshot(name, snapshot, getCachedAppStateSyncKey, initialVersionMap[name], appStateMacVerification.snapshot, logger);
|
|
458
530
|
states[name] = newState;
|
|
459
531
|
Object.assign(globalMutationMap, mutationMap);
|
|
460
532
|
logger.info(`restored state of ${name} from snapshot to v${newState.version} with mutations`);
|
|
@@ -462,7 +534,7 @@ export const makeChatsSocket = (config) => {
|
|
|
462
534
|
}
|
|
463
535
|
// only process if there are syncd patches
|
|
464
536
|
if (patches.length) {
|
|
465
|
-
const { state: newState, mutationMap } = await decodePatches(name, patches, states[name],
|
|
537
|
+
const { state: newState, mutationMap } = await decodePatches(name, patches, states[name], getCachedAppStateSyncKey, config.options, initialVersionMap[name], logger, appStateMacVerification.patch);
|
|
466
538
|
await authState.keys.set({ 'app-state-sync-version': { [name]: newState } });
|
|
467
539
|
logger.info(`synced ${name} to v${newState.version}`);
|
|
468
540
|
initialVersionMap[name] = newState.version;
|
|
@@ -477,19 +549,37 @@ export const makeChatsSocket = (config) => {
|
|
|
477
549
|
}
|
|
478
550
|
}
|
|
479
551
|
catch (error) {
|
|
480
|
-
// if retry attempts overshoot
|
|
481
|
-
// or key not found
|
|
482
|
-
const isIrrecoverableError = attemptsMap[name] >= MAX_SYNC_ATTEMPTS ||
|
|
483
|
-
error.output?.statusCode === 404 ||
|
|
484
|
-
error.name === 'TypeError';
|
|
485
|
-
logger.info({ name, error: error.stack }, `failed to sync state from version${isIrrecoverableError ? '' : ', removing and trying from scratch'}`);
|
|
486
|
-
await authState.keys.set({ 'app-state-sync-version': { [name]: null } });
|
|
487
|
-
// increment number of retries
|
|
488
552
|
attemptsMap[name] = (attemptsMap[name] || 0) + 1;
|
|
489
|
-
|
|
490
|
-
|
|
553
|
+
const logData = {
|
|
554
|
+
name,
|
|
555
|
+
attempt: attemptsMap[name],
|
|
556
|
+
version: states[name].version,
|
|
557
|
+
statusCode: error.output?.statusCode,
|
|
558
|
+
errorType: error.name,
|
|
559
|
+
error: error.stack
|
|
560
|
+
};
|
|
561
|
+
if (isMissingKeyError(error) && attemptsMap[name] >= MAX_SYNC_ATTEMPTS) {
|
|
562
|
+
// WA Web treats missing keys as "Blocked" — park the collection
|
|
563
|
+
// until the key arrives via APP_STATE_SYNC_KEY_SHARE.
|
|
564
|
+
logger.warn(logData, `${name} blocked on missing key from v${states[name].version}, parking after ${attemptsMap[name]} attempts`);
|
|
565
|
+
blockedCollections.add(name);
|
|
491
566
|
collectionsToHandle.delete(name);
|
|
492
567
|
}
|
|
568
|
+
else if (isMissingKeyError(error)) {
|
|
569
|
+
// Retry with a snapshot which may use a different key.
|
|
570
|
+
logger.info(logData, `${name} blocked on missing key from v${states[name].version}, retrying with snapshot`);
|
|
571
|
+
forceSnapshotCollections.add(name);
|
|
572
|
+
}
|
|
573
|
+
else if (isAppStateSyncIrrecoverable(error, attemptsMap[name])) {
|
|
574
|
+
logger.warn(logData, `failed to sync ${name} from v${states[name].version}, giving up`);
|
|
575
|
+
collectionsToHandle.delete(name);
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
logger.info(logData, `failed to sync ${name} from v${states[name].version}, forcing snapshot retry`);
|
|
579
|
+
// force a full snapshot on retry to recover from
|
|
580
|
+
// corrupted local state (e.g. LTHash MAC mismatch)
|
|
581
|
+
forceSnapshotCollections.add(name);
|
|
582
|
+
}
|
|
493
583
|
}
|
|
494
584
|
}
|
|
495
585
|
}
|
|
@@ -505,8 +595,21 @@ export const makeChatsSocket = (config) => {
|
|
|
505
595
|
* type = "image for the high res picture"
|
|
506
596
|
*/
|
|
507
597
|
const profilePictureUrl = async (jid, type = 'preview', timeoutMs) => {
|
|
508
|
-
|
|
509
|
-
|
|
598
|
+
// WA Web only includes tctoken for user JIDs (not groups/newsletters)
|
|
599
|
+
// and never for own profile pic (Chat model for self has no tcToken).
|
|
600
|
+
// Including tctoken for own JID causes the server to never respond.
|
|
601
|
+
const normalizedJid = jidNormalizedUser(jid);
|
|
602
|
+
const isUserJid = isPnUser(normalizedJid) || isLidUser(normalizedJid);
|
|
603
|
+
const me = authState.creds.me;
|
|
604
|
+
const isSelf = me && (normalizedJid === jidNormalizedUser(me.id) || (me.lid && normalizedJid === jidNormalizedUser(me.lid)));
|
|
605
|
+
let tcTokenContent;
|
|
606
|
+
if (serverProps.profilePicPrivacyToken && isUserJid && !isSelf) {
|
|
607
|
+
tcTokenContent = await buildTcTokenFromJid({
|
|
608
|
+
authState,
|
|
609
|
+
jid: normalizedJid,
|
|
610
|
+
getLIDForPN
|
|
611
|
+
});
|
|
612
|
+
}
|
|
510
613
|
jid = jidNormalizedUser(jid);
|
|
511
614
|
const result = await query({
|
|
512
615
|
tag: 'iq',
|
|
@@ -516,7 +619,7 @@ export const makeChatsSocket = (config) => {
|
|
|
516
619
|
type: 'get',
|
|
517
620
|
xmlns: 'w:profile:picture'
|
|
518
621
|
},
|
|
519
|
-
content: tcTokenContent
|
|
622
|
+
content: buildProfilePictureQueryContent(type, tcTokenContent)
|
|
520
623
|
}, timeoutMs);
|
|
521
624
|
const child = getBinaryNodeChild(result, 'picture');
|
|
522
625
|
return child?.attrs?.url;
|
|
@@ -541,12 +644,16 @@ export const makeChatsSocket = (config) => {
|
|
|
541
644
|
};
|
|
542
645
|
const sendPresenceUpdate = async (type, toJid) => {
|
|
543
646
|
const me = authState.creds.me;
|
|
544
|
-
|
|
647
|
+
const isAvailableType = type === 'available';
|
|
648
|
+
if (isAvailableType || type === 'unavailable') {
|
|
545
649
|
if (!me.name) {
|
|
546
650
|
logger.warn('no name present, ignoring presence update request...');
|
|
547
651
|
return;
|
|
548
652
|
}
|
|
549
|
-
ev.emit('connection.update', { isOnline:
|
|
653
|
+
ev.emit('connection.update', { isOnline: isAvailableType });
|
|
654
|
+
if (isAvailableType) {
|
|
655
|
+
void sendUnifiedSession();
|
|
656
|
+
}
|
|
550
657
|
await sendNode({
|
|
551
658
|
tag: 'presence',
|
|
552
659
|
attrs: {
|
|
@@ -578,7 +685,12 @@ export const makeChatsSocket = (config) => {
|
|
|
578
685
|
* @param tcToken token for subscription, use if present
|
|
579
686
|
*/
|
|
580
687
|
const presenceSubscribe = async (toJid) => {
|
|
581
|
-
|
|
688
|
+
// Only include tctoken for user JIDs — groups/newsletters don't use tctokens
|
|
689
|
+
const normalizedToJid = jidNormalizedUser(toJid);
|
|
690
|
+
const isUserJid = isPnUser(normalizedToJid) || isLidUser(normalizedToJid);
|
|
691
|
+
const tcTokenContent = isUserJid
|
|
692
|
+
? await buildTcTokenFromJid({ authState, jid: normalizedToJid, getLIDForPN })
|
|
693
|
+
: undefined;
|
|
582
694
|
return sendNode({
|
|
583
695
|
tag: 'presence',
|
|
584
696
|
attrs: {
|
|
@@ -599,7 +711,8 @@ export const makeChatsSocket = (config) => {
|
|
|
599
711
|
if (tag === 'presence') {
|
|
600
712
|
presence = {
|
|
601
713
|
lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available',
|
|
602
|
-
lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined
|
|
714
|
+
lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined,
|
|
715
|
+
groupOnlineCount: attrs.count ? +attrs.count : undefined
|
|
603
716
|
};
|
|
604
717
|
}
|
|
605
718
|
else if (Array.isArray(content)) {
|
|
@@ -633,7 +746,7 @@ export const makeChatsSocket = (config) => {
|
|
|
633
746
|
logger.debug({ patch: patchCreate }, 'applying app patch');
|
|
634
747
|
await resyncAppState([name], false);
|
|
635
748
|
const { [name]: currentSyncVersion } = await authState.keys.get('app-state-sync-version', [name]);
|
|
636
|
-
initial = currentSyncVersion
|
|
749
|
+
initial = currentSyncVersion ? ensureLTHashStateVersion(currentSyncVersion) : newLTHashState();
|
|
637
750
|
encodeResult = await encodeSyncdPatch(patchCreate, myAppStateKeyId, initial, getAppStateSyncKey);
|
|
638
751
|
const { patch, state } = encodeResult;
|
|
639
752
|
const node = {
|
|
@@ -679,22 +792,21 @@ export const makeChatsSocket = (config) => {
|
|
|
679
792
|
}
|
|
680
793
|
}
|
|
681
794
|
};
|
|
682
|
-
/**
|
|
795
|
+
/** fetch AB props */
|
|
683
796
|
const fetchProps = async () => {
|
|
684
|
-
//TODO: implement both protocol 1 and protocol 2 prop fetching, specially for abKey for WM
|
|
685
797
|
const resultNode = await query({
|
|
686
798
|
tag: 'iq',
|
|
687
799
|
attrs: {
|
|
688
800
|
to: S_WHATSAPP_NET,
|
|
689
|
-
xmlns: '
|
|
801
|
+
xmlns: 'abt',
|
|
690
802
|
type: 'get'
|
|
691
803
|
},
|
|
692
804
|
content: [
|
|
693
805
|
{
|
|
694
806
|
tag: 'props',
|
|
695
807
|
attrs: {
|
|
696
|
-
protocol: '
|
|
697
|
-
hash: authState
|
|
808
|
+
protocol: '1',
|
|
809
|
+
...(authState?.creds?.lastPropHash ? { hash: authState.creds.lastPropHash } : {})
|
|
698
810
|
}
|
|
699
811
|
}
|
|
700
812
|
]
|
|
@@ -709,7 +821,20 @@ export const makeChatsSocket = (config) => {
|
|
|
709
821
|
}
|
|
710
822
|
props = reduceBinaryNodeToDictionary(propsNode, 'prop');
|
|
711
823
|
}
|
|
712
|
-
|
|
824
|
+
// Extract protocol-relevant AB props (only the ones we need)
|
|
825
|
+
const privacyTokenProp = props['10518'] ?? props['privacy_token_sending_on_all_1_on_1_messages'];
|
|
826
|
+
if (privacyTokenProp !== undefined) {
|
|
827
|
+
serverProps.privacyTokenOn1to1 = privacyTokenProp === 'true' || privacyTokenProp === '1';
|
|
828
|
+
}
|
|
829
|
+
const profilePicProp = props['9666'] ?? props['profile_scraping_privacy_token_in_photo_iq'];
|
|
830
|
+
if (profilePicProp !== undefined) {
|
|
831
|
+
serverProps.profilePicPrivacyToken = profilePicProp === 'true' || profilePicProp === '1';
|
|
832
|
+
}
|
|
833
|
+
const lidIssueProp = props['14303'] ?? props['lid_trusted_token_issue_to_lid'];
|
|
834
|
+
if (lidIssueProp !== undefined) {
|
|
835
|
+
serverProps.lidTrustedTokenIssueToLid = lidIssueProp === 'true' || lidIssueProp === '1';
|
|
836
|
+
}
|
|
837
|
+
logger.debug({ serverProps }, 'fetched props');
|
|
713
838
|
return props;
|
|
714
839
|
};
|
|
715
840
|
/**
|
|
@@ -849,6 +974,47 @@ export const makeChatsSocket = (config) => {
|
|
|
849
974
|
? shouldSyncHistoryMessage(historyMsg) &&
|
|
850
975
|
PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
|
|
851
976
|
: false;
|
|
977
|
+
if (historyMsg && shouldProcessHistoryMsg) {
|
|
978
|
+
const syncType = historyMsg.syncType;
|
|
979
|
+
// INITIAL_BOOTSTRAP — fire immediately, no progress check (same as WA Web K function)
|
|
980
|
+
if (syncType === proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP &&
|
|
981
|
+
!historySyncStatus.initialBootstrapComplete) {
|
|
982
|
+
historySyncStatus.initialBootstrapComplete = true;
|
|
983
|
+
ev.emit('messaging-history.status', {
|
|
984
|
+
syncType,
|
|
985
|
+
status: 'complete',
|
|
986
|
+
explicit: true
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
// RECENT with progress === 100 — explicit completion
|
|
990
|
+
if (syncType === proto.HistorySync.HistorySyncType.RECENT &&
|
|
991
|
+
historyMsg.progress === 100 &&
|
|
992
|
+
!historySyncStatus.recentSyncComplete) {
|
|
993
|
+
historySyncStatus.recentSyncComplete = true;
|
|
994
|
+
clearTimeout(historySyncPausedTimeout);
|
|
995
|
+
historySyncPausedTimeout = undefined;
|
|
996
|
+
ev.emit('messaging-history.status', {
|
|
997
|
+
syncType,
|
|
998
|
+
status: 'complete',
|
|
999
|
+
explicit: true
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
// Reset 120s paused timeout on any RECENT chunk (like WA Web's handleChunkProgress)
|
|
1003
|
+
if (syncType === proto.HistorySync.HistorySyncType.RECENT && !historySyncStatus.recentSyncComplete) {
|
|
1004
|
+
clearTimeout(historySyncPausedTimeout);
|
|
1005
|
+
historySyncPausedTimeout = setTimeout(() => {
|
|
1006
|
+
if (!historySyncStatus.recentSyncComplete) {
|
|
1007
|
+
historySyncStatus.recentSyncComplete = true;
|
|
1008
|
+
ev.emit('messaging-history.status', {
|
|
1009
|
+
syncType: proto.HistorySync.HistorySyncType.RECENT,
|
|
1010
|
+
status: 'paused',
|
|
1011
|
+
explicit: false
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
historySyncPausedTimeout = undefined;
|
|
1015
|
+
}, HISTORY_SYNC_PAUSED_TIMEOUT_MS);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
852
1018
|
// State machine: decide on sync and flush
|
|
853
1019
|
if (historyMsg && syncState === SyncState.AwaitingInitialSync) {
|
|
854
1020
|
if (awaitingSyncTimeout) {
|
|
@@ -868,6 +1034,8 @@ export const makeChatsSocket = (config) => {
|
|
|
868
1034
|
}
|
|
869
1035
|
const doAppStateSync = async () => {
|
|
870
1036
|
if (syncState === SyncState.Syncing) {
|
|
1037
|
+
// All collections will be synced, so clear any blocked ones
|
|
1038
|
+
blockedCollections.clear();
|
|
871
1039
|
logger.info('Doing app state sync');
|
|
872
1040
|
await resyncAppState(ALL_WA_PATCH_NAMES, true);
|
|
873
1041
|
// Sync is complete, go online and flush everything
|
|
@@ -927,6 +1095,11 @@ export const makeChatsSocket = (config) => {
|
|
|
927
1095
|
}
|
|
928
1096
|
});
|
|
929
1097
|
ev.on('connection.update', ({ connection, receivedPendingNotifications }) => {
|
|
1098
|
+
if (connection === 'close') {
|
|
1099
|
+
blockedCollections.clear();
|
|
1100
|
+
clearTimeout(historySyncPausedTimeout);
|
|
1101
|
+
historySyncPausedTimeout = undefined;
|
|
1102
|
+
}
|
|
930
1103
|
if (connection === 'open') {
|
|
931
1104
|
if (fireInitQueries) {
|
|
932
1105
|
executeInitQueries().catch(error => onUnexpectedError(error, 'init queries'));
|
|
@@ -936,6 +1109,10 @@ export const makeChatsSocket = (config) => {
|
|
|
936
1109
|
if (!receivedPendingNotifications || syncState !== SyncState.Connecting) {
|
|
937
1110
|
return;
|
|
938
1111
|
}
|
|
1112
|
+
historySyncStatus.initialBootstrapComplete = false;
|
|
1113
|
+
historySyncStatus.recentSyncComplete = false;
|
|
1114
|
+
clearTimeout(historySyncPausedTimeout);
|
|
1115
|
+
historySyncPausedTimeout = undefined;
|
|
939
1116
|
syncState = SyncState.AwaitingInitialSync;
|
|
940
1117
|
logger.info('Connection is now AwaitingInitialSync, buffering events');
|
|
941
1118
|
ev.buffer();
|
|
@@ -948,19 +1125,49 @@ export const makeChatsSocket = (config) => {
|
|
|
948
1125
|
setTimeout(() => ev.flush(), 0);
|
|
949
1126
|
return;
|
|
950
1127
|
}
|
|
951
|
-
|
|
1128
|
+
// On reconnection (accountSyncCounter > 0), the server does not push
|
|
1129
|
+
// history sync notifications — the device already has its data.
|
|
1130
|
+
// Skip the 20s wait and go online immediately.
|
|
1131
|
+
if (authState.creds.accountSyncCounter > 0) {
|
|
1132
|
+
logger.info('Reconnection with existing sync data, skipping history sync wait. Transitioning to Online.');
|
|
1133
|
+
syncState = SyncState.Online;
|
|
1134
|
+
setTimeout(() => ev.flush(), 0);
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
logger.info('First connection, awaiting history sync notification with a 20s timeout.');
|
|
952
1138
|
if (awaitingSyncTimeout) {
|
|
953
1139
|
clearTimeout(awaitingSyncTimeout);
|
|
954
1140
|
}
|
|
955
1141
|
awaitingSyncTimeout = setTimeout(() => {
|
|
956
1142
|
if (syncState === SyncState.AwaitingInitialSync) {
|
|
957
|
-
// TODO: investigate
|
|
958
1143
|
logger.warn('Timeout in AwaitingInitialSync, forcing state to Online and flushing buffer');
|
|
959
1144
|
syncState = SyncState.Online;
|
|
960
1145
|
ev.flush();
|
|
1146
|
+
// Increment so subsequent reconnections skip the 20s wait.
|
|
1147
|
+
// Late-arriving history is still processed via processMessage
|
|
1148
|
+
// regardless of the state machine phase.
|
|
1149
|
+
const accountSyncCounter = (authState.creds.accountSyncCounter || 0) + 1;
|
|
1150
|
+
ev.emit('creds.update', { accountSyncCounter });
|
|
961
1151
|
}
|
|
962
1152
|
}, 20000);
|
|
963
1153
|
});
|
|
1154
|
+
// When an app state sync key arrives (myAppStateKeyId is set) and there are
|
|
1155
|
+
// collections blocked on a missing key, trigger a re-sync for just those collections.
|
|
1156
|
+
// This mirrors WA Web's Blocked → retry-on-key-arrival behavior.
|
|
1157
|
+
ev.on('creds.update', ({ myAppStateKeyId }) => {
|
|
1158
|
+
if (!myAppStateKeyId || blockedCollections.size === 0) {
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
// If we're in the middle of a full sync, doAppStateSync handles all collections
|
|
1162
|
+
if (syncState === SyncState.Syncing) {
|
|
1163
|
+
blockedCollections.clear();
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
const collections = [...blockedCollections];
|
|
1167
|
+
blockedCollections.clear();
|
|
1168
|
+
logger.info({ collections }, 'app state sync key arrived, re-syncing blocked collections');
|
|
1169
|
+
resyncAppState(collections, false).catch(error => onUnexpectedError(error, 'blocked collections resync'));
|
|
1170
|
+
});
|
|
964
1171
|
ev.on('lid-mapping.update', async ({ lid, pn }) => {
|
|
965
1172
|
try {
|
|
966
1173
|
await signalRepository.lidMapping.storeLIDPNMappings([{ lid, pn }]);
|
|
@@ -969,8 +1176,20 @@ export const makeChatsSocket = (config) => {
|
|
|
969
1176
|
logger.warn({ lid, pn, error }, 'Failed to store LID-PN mapping');
|
|
970
1177
|
}
|
|
971
1178
|
});
|
|
1179
|
+
registerSocketEndHandler(() => {
|
|
1180
|
+
if (awaitingSyncTimeout) {
|
|
1181
|
+
clearTimeout(awaitingSyncTimeout);
|
|
1182
|
+
awaitingSyncTimeout = undefined;
|
|
1183
|
+
}
|
|
1184
|
+
if (!config.placeholderResendCache && placeholderResendCache.close) {
|
|
1185
|
+
placeholderResendCache.close();
|
|
1186
|
+
}
|
|
1187
|
+
syncState = SyncState.Connecting;
|
|
1188
|
+
privacySettings = undefined;
|
|
1189
|
+
});
|
|
972
1190
|
return {
|
|
973
1191
|
...sock,
|
|
1192
|
+
serverProps,
|
|
974
1193
|
createCallLink,
|
|
975
1194
|
getBotListV2,
|
|
976
1195
|
messageMutex,
|
|
@@ -1008,6 +1227,7 @@ export const makeChatsSocket = (config) => {
|
|
|
1008
1227
|
cleanDirtyBits,
|
|
1009
1228
|
addOrEditContact,
|
|
1010
1229
|
removeContact,
|
|
1230
|
+
placeholderResendCache,
|
|
1011
1231
|
addLabel,
|
|
1012
1232
|
addChatLabel,
|
|
1013
1233
|
removeChatLabel,
|