@d0v3riz/baileys 7.0.0-rc.8 → 7.0.0-rc13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/README.md +21 -15
  2. package/WAProto/fix-imports.js +74 -18
  3. package/WAProto/index.js +201 -160
  4. package/lib/Defaults/index.d.ts +19 -6
  5. package/lib/Defaults/index.d.ts.map +1 -1
  6. package/lib/Defaults/index.js +24 -10
  7. package/lib/Defaults/index.js.map +1 -1
  8. package/lib/Signal/libsignal.d.ts.map +1 -1
  9. package/lib/Signal/libsignal.js +105 -16
  10. package/lib/Signal/libsignal.js.map +1 -1
  11. package/lib/Signal/lid-mapping.d.ts +8 -8
  12. package/lib/Signal/lid-mapping.d.ts.map +1 -1
  13. package/lib/Signal/lid-mapping.js +176 -70
  14. package/lib/Signal/lid-mapping.js.map +1 -1
  15. package/lib/Socket/Client/websocket.d.ts +1 -1
  16. package/lib/Socket/Client/websocket.d.ts.map +1 -1
  17. package/lib/Socket/Client/websocket.js +5 -1
  18. package/lib/Socket/Client/websocket.js.map +1 -1
  19. package/lib/Socket/business.d.ts +36 -8
  20. package/lib/Socket/business.d.ts.map +1 -1
  21. package/lib/Socket/business.js +11 -8
  22. package/lib/Socket/business.js.map +1 -1
  23. package/lib/Socket/chats.d.ts +29 -5
  24. package/lib/Socket/chats.d.ts.map +1 -1
  25. package/lib/Socket/chats.js +292 -63
  26. package/lib/Socket/chats.js.map +1 -1
  27. package/lib/Socket/communities.d.ts +36 -8
  28. package/lib/Socket/communities.d.ts.map +1 -1
  29. package/lib/Socket/groups.d.ts +27 -5
  30. package/lib/Socket/groups.d.ts.map +1 -1
  31. package/lib/Socket/groups.js +20 -0
  32. package/lib/Socket/groups.js.map +1 -1
  33. package/lib/Socket/index.d.ts +36 -8
  34. package/lib/Socket/index.d.ts.map +1 -1
  35. package/lib/Socket/index.js +0 -6
  36. package/lib/Socket/index.js.map +1 -1
  37. package/lib/Socket/messages-recv.d.ts +40 -11
  38. package/lib/Socket/messages-recv.d.ts.map +1 -1
  39. package/lib/Socket/messages-recv.js +841 -354
  40. package/lib/Socket/messages-recv.js.map +1 -1
  41. package/lib/Socket/messages-send.d.ts +35 -6
  42. package/lib/Socket/messages-send.d.ts.map +1 -1
  43. package/lib/Socket/messages-send.js +263 -92
  44. package/lib/Socket/messages-send.js.map +1 -1
  45. package/lib/Socket/mex.d.ts.map +1 -1
  46. package/lib/Socket/mex.js +2 -0
  47. package/lib/Socket/mex.js.map +1 -1
  48. package/lib/Socket/newsletter.d.ts +27 -5
  49. package/lib/Socket/newsletter.d.ts.map +1 -1
  50. package/lib/Socket/newsletter.js +35 -3
  51. package/lib/Socket/newsletter.js.map +1 -1
  52. package/lib/Socket/socket.d.ts +9 -3
  53. package/lib/Socket/socket.d.ts.map +1 -1
  54. package/lib/Socket/socket.js +124 -39
  55. package/lib/Socket/socket.js.map +1 -1
  56. package/lib/Types/Auth.d.ts +2 -0
  57. package/lib/Types/Auth.d.ts.map +1 -1
  58. package/lib/Types/Call.d.ts +2 -1
  59. package/lib/Types/Call.d.ts.map +1 -1
  60. package/lib/Types/Chat.d.ts +1 -0
  61. package/lib/Types/Chat.d.ts.map +1 -1
  62. package/lib/Types/Contact.d.ts +2 -0
  63. package/lib/Types/Contact.d.ts.map +1 -1
  64. package/lib/Types/Events.d.ts +60 -6
  65. package/lib/Types/Events.d.ts.map +1 -1
  66. package/lib/Types/GroupMetadata.d.ts +4 -0
  67. package/lib/Types/GroupMetadata.d.ts.map +1 -1
  68. package/lib/Types/Message.d.ts +20 -5
  69. package/lib/Types/Message.d.ts.map +1 -1
  70. package/lib/Types/Message.js.map +1 -1
  71. package/lib/Types/{Newsletter.d.ts → Mex.d.ts} +13 -5
  72. package/lib/Types/Mex.d.ts.map +1 -0
  73. package/lib/Types/{Newsletter.js → Mex.js} +11 -3
  74. package/lib/Types/Mex.js.map +1 -0
  75. package/lib/Types/Signal.d.ts +12 -1
  76. package/lib/Types/Signal.d.ts.map +1 -1
  77. package/lib/Types/Socket.d.ts +3 -0
  78. package/lib/Types/Socket.d.ts.map +1 -1
  79. package/lib/Types/State.d.ts +58 -0
  80. package/lib/Types/State.d.ts.map +1 -1
  81. package/lib/Types/State.js +43 -0
  82. package/lib/Types/State.js.map +1 -1
  83. package/lib/Types/index.d.ts +1 -1
  84. package/lib/Types/index.d.ts.map +1 -1
  85. package/lib/Types/index.js +1 -1
  86. package/lib/Types/index.js.map +1 -1
  87. package/lib/Utils/auth-utils.d.ts +5 -0
  88. package/lib/Utils/auth-utils.d.ts.map +1 -1
  89. package/lib/Utils/auth-utils.js +65 -20
  90. package/lib/Utils/auth-utils.js.map +1 -1
  91. package/lib/Utils/chat-utils.d.ts +31 -1
  92. package/lib/Utils/chat-utils.d.ts.map +1 -1
  93. package/lib/Utils/chat-utils.js +180 -71
  94. package/lib/Utils/chat-utils.js.map +1 -1
  95. package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
  96. package/lib/Utils/companion-reg-client-utils.d.ts.map +1 -0
  97. package/lib/Utils/companion-reg-client-utils.js +35 -0
  98. package/lib/Utils/companion-reg-client-utils.js.map +1 -0
  99. package/lib/Utils/crypto.d.ts +4 -8
  100. package/lib/Utils/crypto.d.ts.map +1 -1
  101. package/lib/Utils/crypto.js +2 -26
  102. package/lib/Utils/crypto.js.map +1 -1
  103. package/lib/Utils/decode-wa-message.d.ts +18 -0
  104. package/lib/Utils/decode-wa-message.d.ts.map +1 -1
  105. package/lib/Utils/decode-wa-message.js +34 -0
  106. package/lib/Utils/decode-wa-message.js.map +1 -1
  107. package/lib/Utils/event-buffer.d.ts +2 -0
  108. package/lib/Utils/event-buffer.d.ts.map +1 -1
  109. package/lib/Utils/event-buffer.js +82 -8
  110. package/lib/Utils/event-buffer.js.map +1 -1
  111. package/lib/Utils/generics.d.ts +2 -0
  112. package/lib/Utils/generics.d.ts.map +1 -1
  113. package/lib/Utils/generics.js +13 -1
  114. package/lib/Utils/generics.js.map +1 -1
  115. package/lib/Utils/history.d.ts +8 -3
  116. package/lib/Utils/history.d.ts.map +1 -1
  117. package/lib/Utils/history.js +58 -14
  118. package/lib/Utils/history.js.map +1 -1
  119. package/lib/Utils/identity-change-handler.d.ts +44 -0
  120. package/lib/Utils/identity-change-handler.d.ts.map +1 -0
  121. package/lib/Utils/identity-change-handler.js +50 -0
  122. package/lib/Utils/identity-change-handler.js.map +1 -0
  123. package/lib/Utils/index.d.ts +3 -0
  124. package/lib/Utils/index.d.ts.map +1 -1
  125. package/lib/Utils/index.js +3 -0
  126. package/lib/Utils/index.js.map +1 -1
  127. package/lib/Utils/link-preview.js +2 -2
  128. package/lib/Utils/link-preview.js.map +1 -1
  129. package/lib/Utils/lt-hash.d.ts +7 -12
  130. package/lib/Utils/lt-hash.d.ts.map +1 -1
  131. package/lib/Utils/lt-hash.js +2 -42
  132. package/lib/Utils/lt-hash.js.map +1 -1
  133. package/lib/Utils/make-mutex.d.ts +1 -0
  134. package/lib/Utils/make-mutex.d.ts.map +1 -1
  135. package/lib/Utils/make-mutex.js +20 -27
  136. package/lib/Utils/make-mutex.js.map +1 -1
  137. package/lib/Utils/message-retry-manager.d.ts +35 -2
  138. package/lib/Utils/message-retry-manager.d.ts.map +1 -1
  139. package/lib/Utils/message-retry-manager.js +98 -5
  140. package/lib/Utils/message-retry-manager.js.map +1 -1
  141. package/lib/Utils/messages-media.d.ts +22 -3
  142. package/lib/Utils/messages-media.d.ts.map +1 -1
  143. package/lib/Utils/messages-media.js +169 -44
  144. package/lib/Utils/messages-media.js.map +1 -1
  145. package/lib/Utils/messages.d.ts +2 -0
  146. package/lib/Utils/messages.d.ts.map +1 -1
  147. package/lib/Utils/messages.js +70 -27
  148. package/lib/Utils/messages.js.map +1 -1
  149. package/lib/Utils/noise-handler.d.ts +4 -4
  150. package/lib/Utils/noise-handler.d.ts.map +1 -1
  151. package/lib/Utils/noise-handler.js +139 -85
  152. package/lib/Utils/noise-handler.js.map +1 -1
  153. package/lib/Utils/offline-node-processor.d.ts +17 -0
  154. package/lib/Utils/offline-node-processor.d.ts.map +1 -0
  155. package/lib/Utils/offline-node-processor.js +40 -0
  156. package/lib/Utils/offline-node-processor.js.map +1 -0
  157. package/lib/Utils/process-message.d.ts.map +1 -1
  158. package/lib/Utils/process-message.js +165 -15
  159. package/lib/Utils/process-message.js.map +1 -1
  160. package/lib/Utils/reporting-utils.d.ts +11 -0
  161. package/lib/Utils/reporting-utils.d.ts.map +1 -0
  162. package/lib/Utils/reporting-utils.js +258 -0
  163. package/lib/Utils/reporting-utils.js.map +1 -0
  164. package/lib/Utils/signal.d.ts +13 -0
  165. package/lib/Utils/signal.d.ts.map +1 -1
  166. package/lib/Utils/signal.js +42 -0
  167. package/lib/Utils/signal.js.map +1 -1
  168. package/lib/Utils/stanza-ack.d.ts +11 -0
  169. package/lib/Utils/stanza-ack.d.ts.map +1 -0
  170. package/lib/Utils/stanza-ack.js +38 -0
  171. package/lib/Utils/stanza-ack.js.map +1 -0
  172. package/lib/Utils/sync-action-utils.d.ts +19 -0
  173. package/lib/Utils/sync-action-utils.d.ts.map +1 -0
  174. package/lib/Utils/sync-action-utils.js +49 -0
  175. package/lib/Utils/sync-action-utils.js.map +1 -0
  176. package/lib/Utils/tc-token-utils.d.ts +37 -0
  177. package/lib/Utils/tc-token-utils.d.ts.map +1 -0
  178. package/lib/Utils/tc-token-utils.js +163 -0
  179. package/lib/Utils/tc-token-utils.js.map +1 -0
  180. package/lib/Utils/validate-connection.d.ts.map +1 -1
  181. package/lib/Utils/validate-connection.js +4 -1
  182. package/lib/Utils/validate-connection.js.map +1 -1
  183. package/lib/WABinary/decode.d.ts.map +1 -1
  184. package/lib/WABinary/decode.js +24 -0
  185. package/lib/WABinary/decode.js.map +1 -1
  186. package/lib/WABinary/encode.js +5 -1
  187. package/lib/WABinary/encode.js.map +1 -1
  188. package/lib/WABinary/generic-utils.d.ts +1 -1
  189. package/lib/WABinary/generic-utils.d.ts.map +1 -1
  190. package/lib/WABinary/generic-utils.js +19 -8
  191. package/lib/WABinary/generic-utils.js.map +1 -1
  192. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +1 -1
  193. package/lib/WAUSync/Protocols/USyncContactProtocol.js +26 -3
  194. package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +1 -1
  195. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  196. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts.map +1 -0
  197. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  198. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js.map +1 -0
  199. package/lib/WAUSync/Protocols/index.d.ts +1 -0
  200. package/lib/WAUSync/Protocols/index.d.ts.map +1 -1
  201. package/lib/WAUSync/Protocols/index.js +1 -0
  202. package/lib/WAUSync/Protocols/index.js.map +1 -1
  203. package/lib/WAUSync/USyncQuery.d.ts +1 -0
  204. package/lib/WAUSync/USyncQuery.d.ts.map +1 -1
  205. package/lib/WAUSync/USyncQuery.js +6 -2
  206. package/lib/WAUSync/USyncQuery.js.map +1 -1
  207. package/lib/WAUSync/USyncUser.d.ts +4 -0
  208. package/lib/WAUSync/USyncUser.d.ts.map +1 -1
  209. package/lib/WAUSync/USyncUser.js +8 -0
  210. package/lib/WAUSync/USyncUser.js.map +1 -1
  211. package/package.json +39 -8
  212. package/lib/Types/Newsletter.d.ts.map +0 -1
  213. package/lib/Types/Newsletter.js.map +0 -1
@@ -1,34 +1,56 @@
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
- import { getBinaryNodeChild, getBinaryNodeChildren, jidDecode, jidNormalizedUser, reduceBinaryNodeToDictionary, S_WHATSAPP_NET } from '../WABinary/index.js';
10
+ import { buildTcTokenFromJid } from '../Utils/tc-token-utils.js';
11
+ import { getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isLidUser, isPnUser, jidDecode, jidNormalizedUser, reduceBinaryNodeToDictionary, S_WHATSAPP_NET } from '../WABinary/index.js';
11
12
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
12
13
  import { makeSocket } from './socket.js';
13
- const MAX_SYNC_ATTEMPTS = 2;
14
14
  export const makeChatsSocket = (config) => {
15
15
  const { logger, markOnlineOnConnect, fireInitQueries, appStateMacVerification, shouldIgnoreJid, shouldSyncHistoryMessage, getMessage } = config;
16
16
  const sock = makeSocket(config);
17
- const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError } = sock;
17
+ const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError, sendUnifiedSession, registerSocketEndHandler } = sock;
18
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
18
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
+ };
19
29
  let syncState = SyncState.Connecting;
20
- /** this mutex ensures that the notifications (receipts, messages etc.) are processed in order */
21
- const processingMutex = makeMutex();
30
+ /** this mutex ensures that messages are processed in order */
31
+ const messageMutex = makeMutex();
32
+ /** this mutex ensures that receipts are processed in order */
33
+ const receiptMutex = makeMutex();
34
+ /** this mutex ensures that app state patches are processed in order */
35
+ const appStatePatchMutex = makeMutex();
36
+ /** this mutex ensures that notifications are processed in order */
37
+ const notificationMutex = makeMutex();
22
38
  // Timeout for AwaitingInitialSync state
23
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();
24
49
  const placeholderResendCache = config.placeholderResendCache ||
25
50
  new NodeCache({
26
51
  stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
27
52
  useClones: false
28
53
  });
29
- if (!config.placeholderResendCache) {
30
- config.placeholderResendCache = placeholderResendCache;
31
- }
32
54
  /** helper function to fetch the given app state sync key */
33
55
  const getAppStateSyncKey = async (keyId) => {
34
56
  const { [keyId]: key } = await authState.keys.get('app-state-sync-key', [keyId]);
@@ -251,6 +273,42 @@ export const makeChatsSocket = (config) => {
251
273
  return getBinaryNodeChildren(listNode, 'item').map(n => n.attrs.jid);
252
274
  };
253
275
  const updateBlockStatus = async (jid, action) => {
276
+ const normalizedJid = jidNormalizedUser(jid);
277
+ let lid;
278
+ let pn_jid;
279
+ if (isLidUser(normalizedJid) || isHostedLidUser(normalizedJid)) {
280
+ lid = normalizedJid;
281
+ if (action === 'block') {
282
+ const pn = await signalRepository.lidMapping.getPNForLID(normalizedJid);
283
+ if (!pn) {
284
+ throw new Boom(`Unable to resolve PN JID for LID: ${jid}`, { statusCode: 400 });
285
+ }
286
+ pn_jid = jidNormalizedUser(pn);
287
+ }
288
+ }
289
+ else if (isPnUser(normalizedJid) || isHostedPnUser(normalizedJid)) {
290
+ const mapped = await signalRepository.lidMapping.getLIDForPN(normalizedJid);
291
+ if (!mapped) {
292
+ throw new Boom(`Unable to resolve LID for PN JID: ${jid}`, { statusCode: 400 });
293
+ }
294
+ lid = mapped;
295
+ if (action === 'block') {
296
+ pn_jid = jidNormalizedUser(normalizedJid);
297
+ }
298
+ }
299
+ else {
300
+ throw new Boom(`Invalid jid: ${jid}`, { statusCode: 400 });
301
+ }
302
+ const itemAttrs = {
303
+ action,
304
+ jid: lid
305
+ };
306
+ if (action === 'block') {
307
+ if (!pn_jid) {
308
+ throw new Boom(`pn_jid required for block: ${jid}`, { statusCode: 400 });
309
+ }
310
+ itemAttrs.pn_jid = pn_jid;
311
+ }
254
312
  await query({
255
313
  tag: 'iq',
256
314
  attrs: {
@@ -261,10 +319,7 @@ export const makeChatsSocket = (config) => {
261
319
  content: [
262
320
  {
263
321
  tag: 'item',
264
- attrs: {
265
- action,
266
- jid
267
- }
322
+ attrs: itemAttrs
268
323
  }
269
324
  ]
270
325
  });
@@ -346,6 +401,15 @@ export const makeChatsSocket = (config) => {
346
401
  };
347
402
  };
348
403
  const resyncAppState = ev.createBufferedFunction(async (collections, isInitialSync) => {
404
+ const appStateSyncKeyCache = new Map();
405
+ const getCachedAppStateSyncKey = async (keyId) => {
406
+ if (appStateSyncKeyCache.has(keyId)) {
407
+ return appStateSyncKeyCache.get(keyId) ?? undefined;
408
+ }
409
+ const key = await getAppStateSyncKey(keyId);
410
+ appStateSyncKeyCache.set(keyId, key ?? null);
411
+ return key;
412
+ };
349
413
  // we use this to determine which events to fire
350
414
  // otherwise when we resync from scratch -- all notifications will fire
351
415
  const initialVersionMap = {};
@@ -354,6 +418,9 @@ export const makeChatsSocket = (config) => {
354
418
  const collectionsToHandle = new Set(collections);
355
419
  // in case something goes wrong -- ensure we don't enter a loop that cannot be exited from
356
420
  const attemptsMap = {};
421
+ // collections that failed and need a full snapshot on retry
422
+ // mirrors WA Web's ErrorFatal -> force snapshot behavior
423
+ const forceSnapshotCollections = new Set();
357
424
  // keep executing till all collections are done
358
425
  // sometimes a single patch request will not return all the patches (God knows why)
359
426
  // so we fetch till they're all done (this is determined by the "has_more_patches" flag)
@@ -364,6 +431,7 @@ export const makeChatsSocket = (config) => {
364
431
  const result = await authState.keys.get('app-state-sync-version', [name]);
365
432
  let state = result[name];
366
433
  if (state) {
434
+ state = ensureLTHashStateVersion(state);
367
435
  if (typeof initialVersionMap[name] === 'undefined') {
368
436
  initialVersionMap[name] = state.version;
369
437
  }
@@ -372,14 +440,18 @@ export const makeChatsSocket = (config) => {
372
440
  state = newLTHashState();
373
441
  }
374
442
  states[name] = state;
375
- logger.info(`resyncing ${name} from v${state.version}`);
443
+ const shouldForceSnapshot = forceSnapshotCollections.has(name);
444
+ if (shouldForceSnapshot) {
445
+ forceSnapshotCollections.delete(name);
446
+ }
447
+ logger.info(`resyncing ${name} from v${state.version}${shouldForceSnapshot ? ' (forcing snapshot)' : ''}`);
376
448
  nodes.push({
377
449
  tag: 'collection',
378
450
  attrs: {
379
451
  name,
380
452
  version: state.version.toString(),
381
- // return snapshot if being synced from scratch
382
- return_snapshot: (!state.version).toString()
453
+ // return snapshot if syncing from scratch or forcing after a failed attempt
454
+ return_snapshot: (shouldForceSnapshot || !state.version).toString()
383
455
  }
384
456
  });
385
457
  }
@@ -405,7 +477,7 @@ export const makeChatsSocket = (config) => {
405
477
  const { patches, hasMorePatches, snapshot } = decoded[name];
406
478
  try {
407
479
  if (snapshot) {
408
- const { state: newState, mutationMap } = await decodeSyncdSnapshot(name, snapshot, getAppStateSyncKey, initialVersionMap[name], appStateMacVerification.snapshot);
480
+ const { state: newState, mutationMap } = await decodeSyncdSnapshot(name, snapshot, getCachedAppStateSyncKey, initialVersionMap[name], appStateMacVerification.snapshot, logger);
409
481
  states[name] = newState;
410
482
  Object.assign(globalMutationMap, mutationMap);
411
483
  logger.info(`restored state of ${name} from snapshot to v${newState.version} with mutations`);
@@ -413,7 +485,7 @@ export const makeChatsSocket = (config) => {
413
485
  }
414
486
  // only process if there are syncd patches
415
487
  if (patches.length) {
416
- const { state: newState, mutationMap } = await decodePatches(name, patches, states[name], getAppStateSyncKey, config.options, initialVersionMap[name], logger, appStateMacVerification.patch);
488
+ const { state: newState, mutationMap } = await decodePatches(name, patches, states[name], getCachedAppStateSyncKey, config.options, initialVersionMap[name], logger, appStateMacVerification.patch);
417
489
  await authState.keys.set({ 'app-state-sync-version': { [name]: newState } });
418
490
  logger.info(`synced ${name} to v${newState.version}`);
419
491
  initialVersionMap[name] = newState.version;
@@ -428,19 +500,37 @@ export const makeChatsSocket = (config) => {
428
500
  }
429
501
  }
430
502
  catch (error) {
431
- // if retry attempts overshoot
432
- // or key not found
433
- const isIrrecoverableError = attemptsMap[name] >= MAX_SYNC_ATTEMPTS ||
434
- error.output?.statusCode === 404 ||
435
- error.name === 'TypeError';
436
- logger.info({ name, error: error.stack }, `failed to sync state from version${isIrrecoverableError ? '' : ', removing and trying from scratch'}`);
437
- await authState.keys.set({ 'app-state-sync-version': { [name]: null } });
438
- // increment number of retries
439
503
  attemptsMap[name] = (attemptsMap[name] || 0) + 1;
440
- if (isIrrecoverableError) {
441
- // stop retrying
504
+ const logData = {
505
+ name,
506
+ attempt: attemptsMap[name],
507
+ version: states[name].version,
508
+ statusCode: error.output?.statusCode,
509
+ errorType: error.name,
510
+ error: error.stack
511
+ };
512
+ if (isMissingKeyError(error) && attemptsMap[name] >= MAX_SYNC_ATTEMPTS) {
513
+ // WA Web treats missing keys as "Blocked" — park the collection
514
+ // until the key arrives via APP_STATE_SYNC_KEY_SHARE.
515
+ logger.warn(logData, `${name} blocked on missing key from v${states[name].version}, parking after ${attemptsMap[name]} attempts`);
516
+ blockedCollections.add(name);
517
+ collectionsToHandle.delete(name);
518
+ }
519
+ else if (isMissingKeyError(error)) {
520
+ // Retry with a snapshot which may use a different key.
521
+ logger.info(logData, `${name} blocked on missing key from v${states[name].version}, retrying with snapshot`);
522
+ forceSnapshotCollections.add(name);
523
+ }
524
+ else if (isAppStateSyncIrrecoverable(error, attemptsMap[name])) {
525
+ logger.warn(logData, `failed to sync ${name} from v${states[name].version}, giving up`);
442
526
  collectionsToHandle.delete(name);
443
527
  }
528
+ else {
529
+ logger.info(logData, `failed to sync ${name} from v${states[name].version}, forcing snapshot retry`);
530
+ // force a full snapshot on retry to recover from
531
+ // corrupted local state (e.g. LTHash MAC mismatch)
532
+ forceSnapshotCollections.add(name);
533
+ }
444
534
  }
445
535
  }
446
536
  }
@@ -456,7 +546,23 @@ export const makeChatsSocket = (config) => {
456
546
  * type = "image for the high res picture"
457
547
  */
458
548
  const profilePictureUrl = async (jid, type = 'preview', timeoutMs) => {
459
- // TOOD: Add support for tctoken, existingID, and newsletter + group options
549
+ const baseContent = [{ tag: 'picture', attrs: { type, query: 'url' } }];
550
+ // WA Web only includes tctoken for user JIDs (not groups/newsletters)
551
+ // and never for own profile pic (Chat model for self has no tcToken).
552
+ // Including tctoken for own JID causes the server to never respond.
553
+ const normalizedJid = jidNormalizedUser(jid);
554
+ const isUserJid = isPnUser(normalizedJid) || isLidUser(normalizedJid);
555
+ const me = authState.creds.me;
556
+ const isSelf = me && (normalizedJid === jidNormalizedUser(me.id) || (me.lid && normalizedJid === jidNormalizedUser(me.lid)));
557
+ let content = baseContent;
558
+ if (serverProps.profilePicPrivacyToken && isUserJid && !isSelf) {
559
+ content = await buildTcTokenFromJid({
560
+ authState,
561
+ jid: normalizedJid,
562
+ baseContent,
563
+ getLIDForPN
564
+ });
565
+ }
460
566
  jid = jidNormalizedUser(jid);
461
567
  const result = await query({
462
568
  tag: 'iq',
@@ -466,7 +572,7 @@ export const makeChatsSocket = (config) => {
466
572
  type: 'get',
467
573
  xmlns: 'w:profile:picture'
468
574
  },
469
- content: [{ tag: 'picture', attrs: { type, query: 'url' } }]
575
+ content
470
576
  }, timeoutMs);
471
577
  const child = getBinaryNodeChild(result, 'picture');
472
578
  return child?.attrs?.url;
@@ -491,16 +597,20 @@ export const makeChatsSocket = (config) => {
491
597
  };
492
598
  const sendPresenceUpdate = async (type, toJid) => {
493
599
  const me = authState.creds.me;
494
- if (type === 'available' || type === 'unavailable') {
600
+ const isAvailableType = type === 'available';
601
+ if (isAvailableType || type === 'unavailable') {
495
602
  if (!me.name) {
496
603
  logger.warn('no name present, ignoring presence update request...');
497
604
  return;
498
605
  }
499
- ev.emit('connection.update', { isOnline: type === 'available' });
606
+ ev.emit('connection.update', { isOnline: isAvailableType });
607
+ if (isAvailableType) {
608
+ void sendUnifiedSession();
609
+ }
500
610
  await sendNode({
501
611
  tag: 'presence',
502
612
  attrs: {
503
- name: me.name.replace(/@/g, ''),
613
+ name: me.id.replace(/@/g, ''),
504
614
  type
505
615
  }
506
616
  });
@@ -527,23 +637,23 @@ export const makeChatsSocket = (config) => {
527
637
  * @param toJid the jid to subscribe to
528
638
  * @param tcToken token for subscription, use if present
529
639
  */
530
- const presenceSubscribe = (toJid, tcToken) => sendNode({
531
- tag: 'presence',
532
- attrs: {
533
- to: toJid,
534
- id: generateMessageTag(),
535
- type: 'subscribe'
536
- },
537
- content: tcToken
538
- ? [
539
- {
540
- tag: 'tctoken',
541
- attrs: {},
542
- content: tcToken
543
- }
544
- ]
545
- : undefined
546
- });
640
+ const presenceSubscribe = async (toJid) => {
641
+ // Only include tctoken for user JIDs — groups/newsletters don't use tctokens
642
+ const normalizedToJid = jidNormalizedUser(toJid);
643
+ const isUserJid = isPnUser(normalizedToJid) || isLidUser(normalizedToJid);
644
+ const tcTokenContent = isUserJid
645
+ ? await buildTcTokenFromJid({ authState, jid: normalizedToJid, getLIDForPN })
646
+ : undefined;
647
+ return sendNode({
648
+ tag: 'presence',
649
+ attrs: {
650
+ to: toJid,
651
+ id: generateMessageTag(),
652
+ type: 'subscribe'
653
+ },
654
+ content: tcTokenContent
655
+ });
656
+ };
547
657
  const handlePresenceUpdate = ({ tag, attrs, content }) => {
548
658
  let presence;
549
659
  const jid = attrs.from;
@@ -554,7 +664,8 @@ export const makeChatsSocket = (config) => {
554
664
  if (tag === 'presence') {
555
665
  presence = {
556
666
  lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available',
557
- lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined
667
+ lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined,
668
+ groupOnlineCount: attrs.count ? +attrs.count : undefined
558
669
  };
559
670
  }
560
671
  else if (Array.isArray(content)) {
@@ -583,12 +694,12 @@ export const makeChatsSocket = (config) => {
583
694
  }
584
695
  let initial;
585
696
  let encodeResult;
586
- await processingMutex.mutex(async () => {
697
+ await appStatePatchMutex.mutex(async () => {
587
698
  await authState.keys.transaction(async () => {
588
699
  logger.debug({ patch: patchCreate }, 'applying app patch');
589
700
  await resyncAppState([name], false);
590
701
  const { [name]: currentSyncVersion } = await authState.keys.get('app-state-sync-version', [name]);
591
- initial = currentSyncVersion || newLTHashState();
702
+ initial = currentSyncVersion ? ensureLTHashStateVersion(currentSyncVersion) : newLTHashState();
592
703
  encodeResult = await encodeSyncdPatch(patchCreate, myAppStateKeyId, initial, getAppStateSyncKey);
593
704
  const { patch, state } = encodeResult;
594
705
  const node = {
@@ -634,22 +745,21 @@ export const makeChatsSocket = (config) => {
634
745
  }
635
746
  }
636
747
  };
637
- /** sending non-abt props may fix QR scan fail if server expects */
748
+ /** fetch AB props */
638
749
  const fetchProps = async () => {
639
- //TODO: implement both protocol 1 and protocol 2 prop fetching, specially for abKey for WM
640
750
  const resultNode = await query({
641
751
  tag: 'iq',
642
752
  attrs: {
643
753
  to: S_WHATSAPP_NET,
644
- xmlns: 'w',
754
+ xmlns: 'abt',
645
755
  type: 'get'
646
756
  },
647
757
  content: [
648
758
  {
649
759
  tag: 'props',
650
760
  attrs: {
651
- protocol: '2',
652
- hash: authState?.creds?.lastPropHash || ''
761
+ protocol: '1',
762
+ ...(authState?.creds?.lastPropHash ? { hash: authState.creds.lastPropHash } : {})
653
763
  }
654
764
  }
655
765
  ]
@@ -664,7 +774,20 @@ export const makeChatsSocket = (config) => {
664
774
  }
665
775
  props = reduceBinaryNodeToDictionary(propsNode, 'prop');
666
776
  }
667
- logger.debug('fetched props');
777
+ // Extract protocol-relevant AB props (only the ones we need)
778
+ const privacyTokenProp = props['10518'] ?? props['privacy_token_sending_on_all_1_on_1_messages'];
779
+ if (privacyTokenProp !== undefined) {
780
+ serverProps.privacyTokenOn1to1 = privacyTokenProp === 'true' || privacyTokenProp === '1';
781
+ }
782
+ const profilePicProp = props['9666'] ?? props['profile_scraping_privacy_token_in_photo_iq'];
783
+ if (profilePicProp !== undefined) {
784
+ serverProps.profilePicPrivacyToken = profilePicProp === 'true' || profilePicProp === '1';
785
+ }
786
+ const lidIssueProp = props['14303'] ?? props['lid_trusted_token_issue_to_lid'];
787
+ if (lidIssueProp !== undefined) {
788
+ serverProps.lidTrustedTokenIssueToLid = lidIssueProp === 'true' || lidIssueProp === '1';
789
+ }
790
+ logger.debug({ serverProps }, 'fetched props');
668
791
  return props;
669
792
  };
670
793
  /**
@@ -804,6 +927,47 @@ export const makeChatsSocket = (config) => {
804
927
  ? shouldSyncHistoryMessage(historyMsg) &&
805
928
  PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
806
929
  : false;
930
+ if (historyMsg && shouldProcessHistoryMsg) {
931
+ const syncType = historyMsg.syncType;
932
+ // INITIAL_BOOTSTRAP — fire immediately, no progress check (same as WA Web K function)
933
+ if (syncType === proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP &&
934
+ !historySyncStatus.initialBootstrapComplete) {
935
+ historySyncStatus.initialBootstrapComplete = true;
936
+ ev.emit('messaging-history.status', {
937
+ syncType,
938
+ status: 'complete',
939
+ explicit: true
940
+ });
941
+ }
942
+ // RECENT with progress === 100 — explicit completion
943
+ if (syncType === proto.HistorySync.HistorySyncType.RECENT &&
944
+ historyMsg.progress === 100 &&
945
+ !historySyncStatus.recentSyncComplete) {
946
+ historySyncStatus.recentSyncComplete = true;
947
+ clearTimeout(historySyncPausedTimeout);
948
+ historySyncPausedTimeout = undefined;
949
+ ev.emit('messaging-history.status', {
950
+ syncType,
951
+ status: 'complete',
952
+ explicit: true
953
+ });
954
+ }
955
+ // Reset 120s paused timeout on any RECENT chunk (like WA Web's handleChunkProgress)
956
+ if (syncType === proto.HistorySync.HistorySyncType.RECENT && !historySyncStatus.recentSyncComplete) {
957
+ clearTimeout(historySyncPausedTimeout);
958
+ historySyncPausedTimeout = setTimeout(() => {
959
+ if (!historySyncStatus.recentSyncComplete) {
960
+ historySyncStatus.recentSyncComplete = true;
961
+ ev.emit('messaging-history.status', {
962
+ syncType: proto.HistorySync.HistorySyncType.RECENT,
963
+ status: 'paused',
964
+ explicit: false
965
+ });
966
+ }
967
+ historySyncPausedTimeout = undefined;
968
+ }, HISTORY_SYNC_PAUSED_TIMEOUT_MS);
969
+ }
970
+ }
807
971
  // State machine: decide on sync and flush
808
972
  if (historyMsg && syncState === SyncState.AwaitingInitialSync) {
809
973
  if (awaitingSyncTimeout) {
@@ -823,6 +987,8 @@ export const makeChatsSocket = (config) => {
823
987
  }
824
988
  const doAppStateSync = async () => {
825
989
  if (syncState === SyncState.Syncing) {
990
+ // All collections will be synced, so clear any blocked ones
991
+ blockedCollections.clear();
826
992
  logger.info('Doing app state sync');
827
993
  await resyncAppState(ALL_WA_PATCH_NAMES, true);
828
994
  // Sync is complete, go online and flush everything
@@ -882,6 +1048,11 @@ export const makeChatsSocket = (config) => {
882
1048
  }
883
1049
  });
884
1050
  ev.on('connection.update', ({ connection, receivedPendingNotifications }) => {
1051
+ if (connection === 'close') {
1052
+ blockedCollections.clear();
1053
+ clearTimeout(historySyncPausedTimeout);
1054
+ historySyncPausedTimeout = undefined;
1055
+ }
885
1056
  if (connection === 'open') {
886
1057
  if (fireInitQueries) {
887
1058
  executeInitQueries().catch(error => onUnexpectedError(error, 'init queries'));
@@ -891,6 +1062,10 @@ export const makeChatsSocket = (config) => {
891
1062
  if (!receivedPendingNotifications || syncState !== SyncState.Connecting) {
892
1063
  return;
893
1064
  }
1065
+ historySyncStatus.initialBootstrapComplete = false;
1066
+ historySyncStatus.recentSyncComplete = false;
1067
+ clearTimeout(historySyncPausedTimeout);
1068
+ historySyncPausedTimeout = undefined;
894
1069
  syncState = SyncState.AwaitingInitialSync;
895
1070
  logger.info('Connection is now AwaitingInitialSync, buffering events');
896
1071
  ev.buffer();
@@ -903,24 +1078,77 @@ export const makeChatsSocket = (config) => {
903
1078
  setTimeout(() => ev.flush(), 0);
904
1079
  return;
905
1080
  }
906
- logger.info('History sync is enabled, awaiting notification with a 20s timeout.');
1081
+ // On reconnection (accountSyncCounter > 0), the server does not push
1082
+ // history sync notifications — the device already has its data.
1083
+ // Skip the 20s wait and go online immediately.
1084
+ if (authState.creds.accountSyncCounter > 0) {
1085
+ logger.info('Reconnection with existing sync data, skipping history sync wait. Transitioning to Online.');
1086
+ syncState = SyncState.Online;
1087
+ setTimeout(() => ev.flush(), 0);
1088
+ return;
1089
+ }
1090
+ logger.info('First connection, awaiting history sync notification with a 20s timeout.');
907
1091
  if (awaitingSyncTimeout) {
908
1092
  clearTimeout(awaitingSyncTimeout);
909
1093
  }
910
1094
  awaitingSyncTimeout = setTimeout(() => {
911
1095
  if (syncState === SyncState.AwaitingInitialSync) {
912
- // TODO: investigate
913
1096
  logger.warn('Timeout in AwaitingInitialSync, forcing state to Online and flushing buffer');
914
1097
  syncState = SyncState.Online;
915
1098
  ev.flush();
1099
+ // Increment so subsequent reconnections skip the 20s wait.
1100
+ // Late-arriving history is still processed via processMessage
1101
+ // regardless of the state machine phase.
1102
+ const accountSyncCounter = (authState.creds.accountSyncCounter || 0) + 1;
1103
+ ev.emit('creds.update', { accountSyncCounter });
916
1104
  }
917
1105
  }, 20000);
918
1106
  });
1107
+ // When an app state sync key arrives (myAppStateKeyId is set) and there are
1108
+ // collections blocked on a missing key, trigger a re-sync for just those collections.
1109
+ // This mirrors WA Web's Blocked → retry-on-key-arrival behavior.
1110
+ ev.on('creds.update', ({ myAppStateKeyId }) => {
1111
+ if (!myAppStateKeyId || blockedCollections.size === 0) {
1112
+ return;
1113
+ }
1114
+ // If we're in the middle of a full sync, doAppStateSync handles all collections
1115
+ if (syncState === SyncState.Syncing) {
1116
+ blockedCollections.clear();
1117
+ return;
1118
+ }
1119
+ const collections = [...blockedCollections];
1120
+ blockedCollections.clear();
1121
+ logger.info({ collections }, 'app state sync key arrived, re-syncing blocked collections');
1122
+ resyncAppState(collections, false).catch(error => onUnexpectedError(error, 'blocked collections resync'));
1123
+ });
1124
+ ev.on('lid-mapping.update', async ({ lid, pn }) => {
1125
+ try {
1126
+ await signalRepository.lidMapping.storeLIDPNMappings([{ lid, pn }]);
1127
+ }
1128
+ catch (error) {
1129
+ logger.warn({ lid, pn, error }, 'Failed to store LID-PN mapping');
1130
+ }
1131
+ });
1132
+ registerSocketEndHandler(() => {
1133
+ if (awaitingSyncTimeout) {
1134
+ clearTimeout(awaitingSyncTimeout);
1135
+ awaitingSyncTimeout = undefined;
1136
+ }
1137
+ if (!config.placeholderResendCache && placeholderResendCache.close) {
1138
+ placeholderResendCache.close();
1139
+ }
1140
+ syncState = SyncState.Connecting;
1141
+ privacySettings = undefined;
1142
+ });
919
1143
  return {
920
1144
  ...sock,
1145
+ serverProps,
921
1146
  createCallLink,
922
1147
  getBotListV2,
923
- processingMutex,
1148
+ messageMutex,
1149
+ receiptMutex,
1150
+ appStatePatchMutex,
1151
+ notificationMutex,
924
1152
  fetchPrivacySettings,
925
1153
  upsertMessage,
926
1154
  appPatch,
@@ -951,6 +1179,7 @@ export const makeChatsSocket = (config) => {
951
1179
  cleanDirtyBits,
952
1180
  addOrEditContact,
953
1181
  removeContact,
1182
+ placeholderResendCache,
954
1183
  addLabel,
955
1184
  addChatLabel,
956
1185
  removeChatLabel,