@badzz88/baileys 8.5.4 → 8.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.
Files changed (107) hide show
  1. package/README.md +1 -1
  2. package/WAProto/WAProto.proto +75 -1256
  3. package/WAProto/index.d.ts +19729 -36122
  4. package/WAProto/index.js +132193 -178538
  5. package/engine-requirements.js +15 -7
  6. package/lib/Defaults/index.js +14 -22
  7. package/lib/Signal/Group/group_cipher.js +5 -5
  8. package/lib/Signal/Group/keyhelper.js +4 -4
  9. package/lib/Signal/Group/sender-chain-key.js +2 -2
  10. package/lib/Signal/Group/sender-key-distribution-message.js +1 -1
  11. package/lib/Signal/Group/sender-key-message.js +3 -3
  12. package/lib/Signal/Group/sender-key-record.js +1 -1
  13. package/lib/Signal/Group/sender-message-key.js +5 -5
  14. package/lib/Signal/libsignal.js +28 -39
  15. package/lib/Signal/lid-mapping.js +8 -28
  16. package/lib/Socket/aigroups.js +5 -5
  17. package/lib/Socket/business.js +11 -3
  18. package/lib/Socket/chats.js +319 -668
  19. package/lib/Socket/communities.js +89 -85
  20. package/lib/Socket/graphql.js +228 -435
  21. package/lib/Socket/groups.js +211 -132
  22. package/lib/Socket/index.js +7 -9
  23. package/lib/Socket/interactive-handler.js +60 -117
  24. package/lib/Socket/interop.js +175 -56
  25. package/lib/Socket/luxu.js +387 -0
  26. package/lib/Socket/managed-account.js +48 -79
  27. package/lib/Socket/messages-recv.js +340 -898
  28. package/lib/Socket/messages-send.js +160 -462
  29. package/lib/Socket/mex.js +53 -40
  30. package/lib/Socket/newsletter.js +295 -291
  31. package/lib/Socket/privacy.js +265 -77
  32. package/lib/Socket/registration.js +157 -164
  33. package/lib/Socket/socket.js +199 -181
  34. package/lib/Socket/text-router.js +83 -0
  35. package/lib/Socket/username.js +227 -144
  36. package/lib/Store/keyed-db.js +108 -0
  37. package/lib/Store/make-cache-manager-store.js +2 -2
  38. package/lib/Store/make-in-memory-store.js +141 -25
  39. package/lib/Types/Auth.js +3 -3
  40. package/lib/Types/Label.js +1 -1
  41. package/lib/Types/LabelAssociation.js +1 -1
  42. package/lib/Types/Mex.js +112 -0
  43. package/lib/Types/Newsletter.js +65 -96
  44. package/lib/Types/State.js +4 -4
  45. package/lib/Types/index.js +1 -1
  46. package/lib/Utils/auth-utils.js +57 -52
  47. package/lib/Utils/browser-utils.js +1 -1
  48. package/lib/Utils/business.js +6 -2
  49. package/lib/Utils/chat-utils.js +38 -102
  50. package/lib/Utils/command-loader.d.ts +31 -0
  51. package/lib/Utils/command-loader.js +100 -0
  52. package/lib/Utils/companion-reg-client-utils.js +42 -0
  53. package/lib/Utils/crypto.js +84 -54
  54. package/lib/Utils/curve25519-js.js +275 -0
  55. package/lib/Utils/decode-wa-message.js +83 -316
  56. package/lib/Utils/event-buffer.js +61 -37
  57. package/lib/Utils/generics.js +103 -157
  58. package/lib/Utils/group-history.js +12 -3
  59. package/lib/Utils/history.js +15 -15
  60. package/lib/Utils/index.js +5 -1
  61. package/lib/Utils/link-preview.js +8 -3
  62. package/lib/Utils/lt-hash.js +35 -2
  63. package/lib/Utils/make-mutex.js +1 -1
  64. package/lib/Utils/message-retry-manager.js +92 -27
  65. package/lib/Utils/messages-media.js +130 -159
  66. package/lib/Utils/messages.js +566 -144
  67. package/lib/Utils/meta-ai-msmsg.js +202 -62
  68. package/lib/Utils/native-bridge.js +82 -0
  69. package/lib/Utils/noise-handler.js +21 -19
  70. package/lib/Utils/offline-node-processor.js +8 -3
  71. package/lib/Utils/pre-key-manager.js +23 -13
  72. package/lib/Utils/process-message.js +106 -197
  73. package/lib/Utils/reporting-utils.js +2 -2
  74. package/lib/Utils/session-pool.d.ts +16 -0
  75. package/lib/Utils/session-pool.js +94 -0
  76. package/lib/Utils/signal.js +63 -28
  77. package/lib/Utils/stanza-ack.js +12 -39
  78. package/lib/Utils/sticker.d.ts +17 -0
  79. package/lib/Utils/sticker.js +145 -0
  80. package/lib/Utils/sync-action-utils.js +7 -4
  81. package/lib/Utils/tc-token-utils.js +25 -20
  82. package/lib/Utils/use-multi-file-auth-state.js +14 -30
  83. package/lib/Utils/validate-connection.js +19 -59
  84. package/lib/Utils/view-once-cache.d.ts +9 -0
  85. package/lib/Utils/view-once-cache.js +79 -0
  86. package/lib/WABinary/decode.js +16 -50
  87. package/lib/WABinary/encode.js +224 -54
  88. package/lib/WABinary/generic-utils.js +97 -7
  89. package/lib/WABinary/jid-utils.js +14 -27
  90. package/lib/WAM/encode.js +7 -7
  91. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +1 -58
  92. package/lib/WAUSync/Protocols/USyncContactProtocol.js +1 -7
  93. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +5 -13
  94. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +4 -1
  95. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +7 -20
  96. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +1 -1
  97. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +2 -2
  98. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +2 -7
  99. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +1 -1
  100. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +1 -2
  101. package/lib/WAUSync/Protocols/index.js +0 -1
  102. package/lib/WAUSync/USyncQuery.js +6 -77
  103. package/lib/WAUSync/USyncUser.js +8 -16
  104. package/lib/WAUSync/index.js +0 -1
  105. package/lib/antiban.js +854 -608
  106. package/lib/index.js +1 -1
  107. package/package.json +20 -16
@@ -15,7 +15,6 @@ const State_1 = require('../Types/State')
15
15
  const Utils_1 = require('../Utils')
16
16
  const make_mutex_1 = require('../Utils/make-mutex')
17
17
  const process_message_1 = __importDefault(require('../Utils/process-message'))
18
- const { unwrapSecretEncryptedMessage } = require('../Utils/process-message')
19
18
  const tc_token_utils_1 = require('../Utils/tc-token-utils')
20
19
  const WABinary_1 = require('../WABinary')
21
20
  const WAUSync_1 = require('../WAUSync')
@@ -63,47 +62,45 @@ const makeChatsSocket = config => {
63
62
  } = sock
64
63
  const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping)
65
64
  let privacySettings
66
-
65
+ /** Server-assigned AB props for protocol behavior. */
67
66
  const serverProps = {
68
-
67
+ /** AB prop 10518: gate tctoken on 1:1 messages. Default true (safe: avoids 463). */
69
68
  privacyTokenOn1to1: true,
70
-
69
+ /** AB prop 9666: gate tctoken on profile picture IQs. WA Web default: true. */
71
70
  profilePicPrivacyToken: true,
72
-
71
+ /** AB prop 14303: issue tctokens to LID instead of PN. WA Web default: false. */
73
72
  lidTrustedTokenIssueToLid: false
74
73
  }
75
74
  let syncState = State_1.SyncState.Connecting
76
-
75
+ /** this mutex ensures that messages are processed in order */
77
76
  const messageMutex = (0, make_mutex_1.makeMutex)()
78
-
77
+ /** this mutex ensures that receipts are processed in order */
79
78
  const receiptMutex = (0, make_mutex_1.makeMutex)()
80
-
79
+ /** this mutex ensures that app state patches are processed in order */
81
80
  const appStatePatchMutex = (0, make_mutex_1.makeMutex)()
82
-
81
+ /** this mutex ensures that notifications are processed in order */
83
82
  const notificationMutex = (0, make_mutex_1.makeMutex)()
84
-
83
+ // Timeout for AwaitingInitialSync state
85
84
  let awaitingSyncTimeout
86
-
85
+ // In-memory history sync completion tracking (resets on reconnection)
87
86
  const historySyncStatus = {
88
87
  initialBootstrapComplete: false,
89
88
  recentSyncComplete: false
90
89
  }
91
90
  let historySyncPausedTimeout
92
-
93
-
91
+ // Collections blocked on missing app state sync keys (mirrors WA Web's "Blocked" state).
92
+ // When a key arrives via APP_STATE_SYNC_KEY_SHARE, these are re-synced.
94
93
  const blockedCollections = new Set()
95
-
96
- const _nlServerIdCache = new Map()
97
94
  const placeholderResendCache =
98
95
  config.placeholderResendCache ||
99
96
  new node_cache_1.default({
100
- stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY,
97
+ stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
101
98
  useClones: false
102
99
  })
103
100
  if (!config.placeholderResendCache) {
104
101
  config.placeholderResendCache = placeholderResendCache
105
102
  }
106
-
103
+ /** helper function to fetch the given app state sync key */
107
104
  const getAppStateSyncKey = async keyId => {
108
105
  const { [keyId]: key } = await authState.keys.get('app-state-sync-key', [keyId])
109
106
  return key
@@ -119,67 +116,11 @@ const makeChatsSocket = config => {
119
116
  },
120
117
  content: [{ tag: 'privacy', attrs: {} }]
121
118
  })
122
- const privacyNode = content?.[0]
123
- privacySettings = (0, WABinary_1.reduceBinaryNodeToDictionary)(privacyNode, 'category')
124
-
125
-
126
-
127
- const onlinePrivacy =
128
- privacySettings['online'] ||
129
- (0, WABinary_1.getBinaryNodeChild)(privacyNode, 'online_privacy_setting')?.attrs?.value ||
130
- 'all'
131
-
132
-
133
- const enhancedBlockEnabled =
134
- privacyNode?.attrs?.enhanced_block_enabled === 'true' ||
135
- (0, WABinary_1.getBinaryNodeChild)(privacyNode, 'enhanced_block')?.attrs?.enabled === 'true'
136
-
137
-
138
- const mdPrivacyV2 =
139
- privacyNode?.attrs?.md_privacy_v2 === 'true' ||
140
- (0, WABinary_1.getBinaryNodeChild)(privacyNode, 'md_privacy_v2')?.attrs?.value === 'true'
141
-
142
-
143
- const syncdClearChatDeleteChatEnabled =
144
- privacyNode?.attrs?.syncd_clear_chat_delete_chat_enabled === 'true' ||
145
- (0, WABinary_1.getBinaryNodeChild)(privacyNode, 'syncd_clear_chat')?.attrs?.delete_chat_enabled === 'true'
146
-
147
-
148
- const syncdAntiTamperingEnabled =
149
- privacyNode?.attrs?.syncd_anti_tampering_fatal_exception_enabled === 'true' ||
150
- (0, WABinary_1.getBinaryNodeChild)(privacyNode, 'syncd_anti_tampering')?.attrs?.fatal_exception_enabled ===
151
- 'true'
152
-
153
-
154
- const keyRotationEnabled =
155
- privacyNode?.attrs?.syncd_key_rotation_enabled === 'true' ||
156
- (0, WABinary_1.getBinaryNodeChild)(privacyNode, 'syncd_key_rotation')?.attrs?.enabled === 'true'
157
-
158
-
159
- ev.emit('settings.update', {
160
- onlinePrivacy,
161
- enhancedBlockEnabled,
162
- mdPrivacyV2,
163
- syncdClearChatDeleteChatEnabled,
164
- ...(syncdAntiTamperingEnabled ? { syncdAntiTamperingEnabled: true } : {}),
165
- keyRotationEnabled
166
- })
167
-
168
-
169
- ev.emit('creds.update', {
170
- privacySettings: {
171
- onlinePrivacy,
172
- enhancedBlockEnabled,
173
- mdPrivacyV2,
174
- syncdClearChatDeleteChatEnabled,
175
- syncdAntiTamperingEnabled,
176
- keyRotationEnabled
177
- }
178
- })
119
+ privacySettings = (0, WABinary_1.reduceBinaryNodeToDictionary)(content?.[0], 'category')
179
120
  }
180
121
  return privacySettings
181
122
  }
182
-
123
+ /** helper function to run a privacy IQ query */
183
124
  const privacyQuery = async (name, value) => {
184
125
  await query({
185
126
  tag: 'iq',
@@ -220,7 +161,11 @@ const makeChatsSocket = config => {
220
161
  const updateStatusPrivacy = async value => {
221
162
  await privacyQuery('status', value)
222
163
  }
223
-
164
+ /**
165
+ * Fetch status privacy settings via `xmlns="status"` IQ GET.
166
+ * Returns the current distribution type and any custom lists.
167
+ * Source: GetStatusPrivacyJob.java, feature flag 3843 controls retry.
168
+ */
224
169
  const getStatusPrivacy = async () => {
225
170
  const result = await query({
226
171
  tag: 'iq',
@@ -234,24 +179,31 @@ const makeChatsSocket = config => {
234
179
  const privacyNode = result?.content?.[0]
235
180
  if (!privacyNode) return null
236
181
  const lists = []
237
- for (const listNode of privacyNode.content || []) {
182
+ for (const listNode of (privacyNode.content || [])) {
238
183
  const { type, id, listname, emoji, selected, deleted } = listNode.attrs || {}
239
184
  const members = (listNode.content || []).map(u => u.attrs?.jid).filter(Boolean)
240
185
  lists.push({ type, id, listname, emoji, selected: selected === 'true', deleted: deleted === 'true', members })
241
186
  }
242
187
  return lists
243
188
  }
244
-
189
+ /**
190
+ * Set status privacy via `xmlns="status"` IQ SET.
191
+ * Supports simple distribution types and contact-level whitelist/blacklist/customlist.
192
+ *
193
+ * @param {'contacts'|'whitelist'|'blacklist'|'null'} type - Distribution type
194
+ * @param {string[]} [jids] - JIDs for the list (whitelist/blacklist)
195
+ * @param {Array<{id: string, listname: string, emoji?: string, selected?: boolean, deleted?: boolean, members?: string[]}>} [customLists]
196
+ */
245
197
  const setStatusPrivacy = async (type, jids = [], customLists = []) => {
246
198
  const content = []
247
-
199
+ // main distribution list
248
200
  const mainList = {
249
201
  tag: 'list',
250
202
  attrs: { type },
251
203
  content: jids.map(jid => ({ tag: 'user', attrs: { jid }, content: [] }))
252
204
  }
253
205
  content.push(mainList)
254
-
206
+ // custom named lists
255
207
  for (const cl of customLists) {
256
208
  const attrs = { type: 'customlist', id: cl.id, listname: cl.listname }
257
209
  if (cl.emoji) attrs.emoji = cl.emoji
@@ -297,7 +249,12 @@ const makeChatsSocket = config => {
297
249
  ]
298
250
  })
299
251
  }
300
-
252
+ /**
253
+ * Fetch broadcast list quota from the server.
254
+ * Source: BroadcastListQuotaProtocol.java — IQ xmlns="w:biz", 32s timeout.
255
+ *
256
+ * Returns: { messagesLeft, totalLimit, isHeavySender, startTs, endTs, resetTs }
257
+ */
301
258
  const fetchBroadcastListQuota = async () => {
302
259
  const result = await query(
303
260
  {
@@ -315,35 +272,12 @@ const makeChatsSocket = config => {
315
272
  const timeframeNode = (0, WABinary_1.getBinaryNodeChild)(result, 'timeframe')
316
273
  if (!limitsNode) return null
317
274
  return {
318
- messagesLeft: parseInt(
319
- limitsNode.attrs?.messages_left ??
320
- (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'messages_left')?.content ??
321
- '0',
322
- 10
323
- ),
324
- totalLimit: parseInt(
325
- limitsNode.attrs?.total_limit ?? (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'total_limit')?.content ?? '0',
326
- 10
327
- ),
328
- isHeavySender:
329
- (limitsNode.attrs?.is_heavy_sender ??
330
- (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'is_heavy_sender')?.content) === 'true',
331
- startTs: parseInt(
332
- timeframeNode?.attrs?.start_ts_s ??
333
- (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'start_ts_s')?.content ??
334
- '0',
335
- 10
336
- ),
337
- endTs: parseInt(
338
- timeframeNode?.attrs?.end_ts_s ?? (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'end_ts_s')?.content ?? '0',
339
- 10
340
- ),
341
- resetTs: parseInt(
342
- timeframeNode?.attrs?.reset_ts_s ??
343
- (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'reset_ts_s')?.content ??
344
- '0',
345
- 10
346
- )
275
+ messagesLeft: parseInt(limitsNode.attrs?.messages_left ?? (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'messages_left')?.content ?? '0'),
276
+ totalLimit: parseInt(limitsNode.attrs?.total_limit ?? (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'total_limit')?.content ?? '0'),
277
+ isHeavySender: (limitsNode.attrs?.is_heavy_sender ?? (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'is_heavy_sender')?.content) === 'true',
278
+ startTs: parseInt(timeframeNode?.attrs?.start_ts_s ?? (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'start_ts_s')?.content ?? '0'),
279
+ endTs: parseInt(timeframeNode?.attrs?.end_ts_s ?? (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'end_ts_s')?.content ?? '0'),
280
+ resetTs: parseInt(timeframeNode?.attrs?.reset_ts_s ?? (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'reset_ts_s')?.content ?? '0')
347
281
  }
348
282
  }
349
283
 
@@ -378,7 +312,11 @@ const makeChatsSocket = config => {
378
312
  }
379
313
  return botList
380
314
  }
381
-
315
+ /**
316
+ * Get the global chat-blocking status (block messages from unknown accounts).
317
+ * Ported from WhatsApp Web's WASmaxPsaChatBlockGetRPC (xmlns `w:comms:chat`).
318
+ * @returns {Promise<'blocked' | 'unblocked' | undefined>}
319
+ */
382
320
  const getChatBlockingStatus = async () => {
383
321
  const result = await query({
384
322
  tag: 'iq',
@@ -394,7 +332,12 @@ const makeChatsSocket = config => {
394
332
  (0, WABinary_1.getBinaryNodeChild)((0, WABinary_1.getBinaryNodeChild)(result, 'query'), 'blocking')
395
333
  return blocking?.attrs?.status
396
334
  }
397
-
335
+ /**
336
+ * Set the global chat-blocking status (block messages from unknown accounts).
337
+ * Ported from WhatsApp Web's WASmaxPsaChatBlockSetRPC (xmlns `w:comms:chat`).
338
+ * @param {'block' | 'unblock'} action
339
+ * @returns {Promise<'blocked' | 'unblocked' | undefined>} the resulting status
340
+ */
398
341
  const updateChatBlockingStatus = async action => {
399
342
  const result = await query({
400
343
  tag: 'iq',
@@ -408,7 +351,12 @@ const makeChatsSocket = config => {
408
351
  const blocking = (0, WABinary_1.getBinaryNodeChild)(result, 'blocking')
409
352
  return blocking?.attrs?.status
410
353
  }
411
-
354
+ /**
355
+ * Get the user's pending TOS disclosures / notices.
356
+ * Ported from WhatsApp Web's WASmaxUserNoticeGetDisclosuresRPC (xmlns `tos`).
357
+ * @param {number} [t] last-seen disclosure timestamp
358
+ * @returns {Promise<Array<Record<string, string>>>} the `<notice>` attributes
359
+ */
412
360
  const getUserDisclosures = async (t = 0) => {
413
361
  const result = await query({
414
362
  tag: 'iq',
@@ -417,65 +365,18 @@ const makeChatsSocket = config => {
417
365
  })
418
366
  return (0, WABinary_1.getBinaryNodeChildren)(result, 'notice').map(n => ({ ...n.attrs }))
419
367
  }
420
-
421
- const acceptTosNotice = async (noticeId, result = '105') => {
422
- await query({
423
- tag: 'iq',
424
- attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'tos', type: 'set' },
425
- content: [{ tag: 'trackable', attrs: { id: String(noticeId), result: String(result) } }]
426
- })
427
- }
428
-
429
- const reportSpam = async (jid, messages, spamFlow = 'contact_info_report', subject) => {
430
- const msgNodes = (messages || []).map(m => ({
431
- tag: 'message',
432
- attrs: {
433
- from: String(jid),
434
- t: String(m.t),
435
- id: String(m.id)
436
- }
437
- }))
438
- await query({
439
- tag: 'iq',
440
- attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'spam', type: 'set' },
441
- content: [
442
- {
443
- tag: 'spam_list',
444
- attrs: {
445
- jid: String(jid),
446
- spam_flow: String(spamFlow),
447
- ...(subject ? { subject: String(subject) } : {})
448
- },
449
- content: msgNodes
450
- }
451
- ]
452
- })
453
- }
454
-
368
+ /**
369
+ * Get the account opt-out list (`optoutlist` IQ). Returns the raw result node.
370
+ */
455
371
  const getOptOutList = async () => {
456
372
  return query({
457
373
  tag: 'iq',
458
374
  attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'optoutlist', type: 'get' }
459
375
  })
460
376
  }
461
-
462
- const signPrivateCredential = async blindedCredential => {
463
- const result = await query({
464
- tag: 'iq',
465
- attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'privatestats', type: 'get' },
466
- content: [
467
- {
468
- tag: 'sign_credential',
469
- attrs: { version: '1' },
470
- content: [{ tag: 'blinded_credential', attrs: {}, content: blindedCredential }]
471
- }
472
- ]
473
- })
474
- const signedNode = (0, WABinary_1.getBinaryNodeChild)(result, 'sign_credential')
475
- const signedBytes = (0, WABinary_1.getBinaryNodeChild)(signedNode, 'signed_credential')
476
- return signedBytes?.content instanceof Uint8Array ? Buffer.from(signedBytes.content) : undefined
477
- }
478
-
377
+ /**
378
+ * Get push-notification settings (`urn:xmpp:whatsapp:push`).
379
+ */
479
380
  const getPushConfig = async () => {
480
381
  const result = await query({
481
382
  tag: 'iq',
@@ -484,7 +385,11 @@ const makeChatsSocket = config => {
484
385
  })
485
386
  return (0, WABinary_1.getBinaryNodeChild)(result, 'settings')
486
387
  }
487
-
388
+ /**
389
+ * Set push-notification config (`urn:xmpp:whatsapp:push`). Mainly web push —
390
+ * pass the FCM-style config (platform / endpoint / auth / p256dh).
391
+ * @param {Record<string, string>} config
392
+ */
488
393
  const setPushConfig = async config => {
489
394
  await query({
490
395
  tag: 'iq',
@@ -497,7 +402,7 @@ const makeChatsSocket = config => {
497
402
  for (const jid of jids) {
498
403
  usyncQuery.withUser(new WAUSync_1.USyncUser().withId(jid))
499
404
  }
500
- const result = await executeUSyncQuery(usyncQuery)
405
+ const result = await sock.executeUSyncQuery(usyncQuery)
501
406
  if (result) {
502
407
  return result.list
503
408
  }
@@ -507,12 +412,12 @@ const makeChatsSocket = config => {
507
412
  for (const jid of jids) {
508
413
  usyncQuery.withUser(new WAUSync_1.USyncUser().withId(jid))
509
414
  }
510
- const result = await executeUSyncQuery(usyncQuery)
415
+ const result = await sock.executeUSyncQuery(usyncQuery)
511
416
  if (result) {
512
417
  return result.list
513
418
  }
514
419
  }
515
-
420
+ /** update the profile picture for yourself or a group */
516
421
  const updateProfilePicture = async (jid, content, dimensions) => {
517
422
  let targetJid
518
423
  if (!jid) {
@@ -521,7 +426,7 @@ const makeChatsSocket = config => {
521
426
  )
522
427
  }
523
428
  if ((0, WABinary_1.jidNormalizedUser)(jid) !== (0, WABinary_1.jidNormalizedUser)(authState.creds.me.id)) {
524
- targetJid = (0, WABinary_1.jidNormalizedUser)(jid)
429
+ targetJid = (0, WABinary_1.jidNormalizedUser)(jid) // in case it is someone other than us
525
430
  } else {
526
431
  targetJid = undefined
527
432
  }
@@ -543,7 +448,7 @@ const makeChatsSocket = config => {
543
448
  ]
544
449
  })
545
450
  }
546
-
451
+ /** remove the profile picture for yourself or a group */
547
452
  const removeProfilePicture = async jid => {
548
453
  let targetJid
549
454
  if (!jid) {
@@ -552,7 +457,7 @@ const makeChatsSocket = config => {
552
457
  )
553
458
  }
554
459
  if ((0, WABinary_1.jidNormalizedUser)(jid) !== (0, WABinary_1.jidNormalizedUser)(authState.creds.me.id)) {
555
- targetJid = (0, WABinary_1.jidNormalizedUser)(jid)
460
+ targetJid = (0, WABinary_1.jidNormalizedUser)(jid) // in case it is someone other than us
556
461
  } else {
557
462
  targetJid = undefined
558
463
  }
@@ -566,7 +471,7 @@ const makeChatsSocket = config => {
566
471
  }
567
472
  })
568
473
  }
569
-
474
+ /** update the profile status for yourself */
570
475
  const updateProfileStatus = async status => {
571
476
  await query({
572
477
  tag: 'iq',
@@ -587,24 +492,17 @@ const makeChatsSocket = config => {
587
492
  const updateProfileName = async name => {
588
493
  await chatModify({ pushNameSetting: name }, '')
589
494
  }
590
-
591
-
592
-
593
-
594
- const fetchBlocklist = async (dhash = null) => {
495
+ const fetchBlocklist = async () => {
595
496
  const result = await query({
596
497
  tag: 'iq',
597
498
  attrs: {
598
499
  xmlns: 'blocklist',
599
500
  to: WABinary_1.S_WHATSAPP_NET,
600
501
  type: 'get'
601
- },
602
- content: dhash ? [{ tag: 'item', attrs: { dhash } }] : null
502
+ }
603
503
  })
604
504
  const listNode = (0, WABinary_1.getBinaryNodeChild)(result, 'list')
605
- const jids = (0, WABinary_1.getBinaryNodeChildren)(listNode, 'item').map(n => n.attrs.jid)
606
- ev.emit('blocklist.set', { blocklist: jids })
607
- return jids
505
+ return (0, WABinary_1.getBinaryNodeChildren)(listNode, 'item').map(n => n.attrs.jid)
608
506
  }
609
507
  const updateBlockStatus = async (jid, action) => {
610
508
  await query({
@@ -626,67 +524,26 @@ const makeChatsSocket = config => {
626
524
  })
627
525
  }
628
526
  const getBusinessProfile = async jid => {
629
-
630
-
631
-
632
- const [results, verifiedNameResult] = await Promise.all([
633
- query({
634
- tag: 'iq',
635
- attrs: {
636
- to: 's.whatsapp.net',
637
- xmlns: 'w:biz',
638
- type: 'get'
639
- },
640
- content: [
641
- {
642
- tag: 'business_profile',
643
- attrs: { v: '116' },
644
- content: [
645
- {
646
- tag: 'profile',
647
- attrs: { jid }
648
- }
649
- ]
650
- }
651
- ]
652
- }),
653
- query({
654
- tag: 'iq',
655
- attrs: {
656
- to: 's.whatsapp.net',
657
- xmlns: 'w:biz',
658
- type: 'get'
659
- },
660
- content: [
661
- {
662
- tag: 'verified_name',
663
- attrs: { jid }
664
- }
665
- ]
666
- })
667
- ])
668
-
669
- const verifiedNameNode = (0, WABinary_1.getBinaryNodeChild)(verifiedNameResult, 'verified_name')
670
- let verifiedNameCert
671
- if (verifiedNameNode) {
672
- const certNode = (0, WABinary_1.getBinaryNodeChild)(verifiedNameNode, 'certificate')
673
- if (certNode) {
674
- const detailsNode = (0, WABinary_1.getBinaryNodeChild)(certNode, 'details')
675
- const localizedNames = (0, WABinary_1.getBinaryNodeChildren)(certNode, 'localized_name').map(n => ({
676
- lg: n.attrs?.lg,
677
- lc: n.attrs?.lc,
678
- verifiedName: n.attrs?.verified_name || n.content?.toString()
679
- }))
680
- verifiedNameCert = {
681
- certSerial: certNode.attrs?.serial ? +certNode.attrs.serial : undefined,
682
- issuer: certNode.attrs?.issuer,
683
- details: {
684
- verifiedName: detailsNode?.attrs?.verified_name || detailsNode?.content?.toString(),
685
- localizedNames
686
- }
527
+ const results = await query({
528
+ tag: 'iq',
529
+ attrs: {
530
+ to: 's.whatsapp.net',
531
+ xmlns: 'w:biz',
532
+ type: 'get'
533
+ },
534
+ content: [
535
+ {
536
+ tag: 'business_profile',
537
+ attrs: { v: '244' },
538
+ content: [
539
+ {
540
+ tag: 'profile',
541
+ attrs: { jid }
542
+ }
543
+ ]
687
544
  }
688
- }
689
- }
545
+ ]
546
+ })
690
547
  const profileNode = (0, WABinary_1.getBinaryNodeChild)(results, 'business_profile')
691
548
  const profiles = (0, WABinary_1.getBinaryNodeChild)(profileNode, 'profile')
692
549
  if (profiles) {
@@ -703,31 +560,9 @@ const makeChatsSocket = config => {
703
560
  ? (0, WABinary_1.getBinaryNodeChildren)(businessHours, 'business_hours_config')
704
561
  : undefined
705
562
  const websiteStr = website?.content?.toString()
706
-
707
-
708
- const catalogStatus = {
709
- exists: profiles.attrs?.catalog_exists === 'true' || profiles.attrs?.catalog_exists === true || false,
710
- sendAll: profiles.attrs?.catalog_send_all === 'true' || profiles.attrs?.catalog_send_all === true || false
711
- }
712
-
713
-
714
- const cartEnabled =
715
- profiles.attrs?.cart_enabled !== undefined
716
- ? profiles.attrs.cart_enabled === 'true' || profiles.attrs.cart_enabled === true
717
- : undefined
718
- const webCartEnabled =
719
- profiles.attrs?.web_cart_enabled !== undefined
720
- ? profiles.attrs.web_cart_enabled === 'true' || profiles.attrs.web_cart_enabled === true
721
- : undefined
722
- const webCartOnOff = profiles.attrs?.web_cart_on_off
723
-
724
-
725
- const commerceExperience = profiles.attrs?.commerce_experience
726
-
727
563
  return {
728
564
  wid: profiles.attrs?.jid,
729
565
  address: address?.content?.toString(),
730
- businessAddress: address?.content?.toString(),
731
566
  description: description?.content?.toString() || '',
732
567
  website: websiteStr ? [websiteStr] : [],
733
568
  email: email?.content?.toString(),
@@ -735,24 +570,7 @@ const makeChatsSocket = config => {
735
570
  business_hours: {
736
571
  timezone: businessHours?.attrs?.timezone,
737
572
  business_config: businessHoursConfig?.map(({ attrs }) => attrs)
738
- },
739
- businessHours: businessHoursConfig
740
- ? {
741
- timezone: businessHours?.attrs?.timezone ?? null,
742
- config: businessHoursConfig.map(({ attrs }) => ({
743
- dayOfWeek: attrs?.day_of_week ?? null,
744
- openTime: attrs?.open_time ?? null,
745
- closeTime: attrs?.close_time ?? null,
746
- mode: attrs?.mode ?? null
747
- }))
748
- }
749
- : null,
750
- catalogStatus,
751
- cartEnabled,
752
- webCartEnabled,
753
- webCartOnOff,
754
- commerceExperience,
755
- verifiedNameCert
573
+ }
756
574
  }
757
575
  }
758
576
  }
@@ -787,13 +605,6 @@ const makeChatsSocket = config => {
787
605
  isInitialSync ? { accountSettings: authState.creds.accountSettings } : undefined,
788
606
  logger
789
607
  )
790
-
791
- const _nctSalt = mutation?.syncAction?.value?.nctSaltSyncAction?.salt
792
- if (_nctSalt?.length) {
793
- authState.keys
794
- .set({ 'nct-salt': { default: Buffer.from(_nctSalt) } })
795
- .catch(err => logger.debug({ err: err?.message }, 'nct: failed to persist salt'))
796
- }
797
608
  }
798
609
  }
799
610
  }
@@ -801,24 +612,24 @@ const makeChatsSocket = config => {
801
612
  const appStateSyncKeyCache = new Map()
802
613
  const getCachedAppStateSyncKey = async keyId => {
803
614
  if (appStateSyncKeyCache.has(keyId)) {
804
- return appStateSyncKeyCache.get(keyId)
615
+ return appStateSyncKeyCache.get(keyId) ?? undefined
805
616
  }
806
617
  const key = await getAppStateSyncKey(keyId)
807
618
  appStateSyncKeyCache.set(keyId, key ?? null)
808
619
  return key
809
620
  }
810
-
811
-
621
+ // we use this to determine which events to fire
622
+ // otherwise when we resync from scratch -- all notifications will fire
812
623
  const initialVersionMap = {}
813
624
  const globalMutationMap = {}
814
625
  await authState.keys.transaction(async () => {
815
626
  const collectionsToHandle = new Set(collections)
816
-
627
+ // in case something goes wrong -- ensure we don't enter a loop that cannot be exited from
817
628
  const attemptsMap = {}
818
629
  const forceSnapshotCollections = new Set()
819
-
820
-
821
-
630
+ // keep executing till all collections are done
631
+ // sometimes a single patch request will not return all the patches (God knows why)
632
+ // so we fetch till they're all done (this is determined by the "has_more_patches" flag)
822
633
  while (collectionsToHandle.size) {
823
634
  const states = {}
824
635
  const nodes = []
@@ -826,7 +637,7 @@ const makeChatsSocket = config => {
826
637
  const result = await authState.keys.get('app-state-sync-version', [name])
827
638
  let state = result[name]
828
639
  if (state) {
829
- if (initialVersionMap[name] === undefined) {
640
+ if (typeof initialVersionMap[name] === 'undefined') {
830
641
  initialVersionMap[name] = state.version
831
642
  }
832
643
  } else {
@@ -843,7 +654,7 @@ const makeChatsSocket = config => {
843
654
  attrs: {
844
655
  name,
845
656
  version: state.version.toString(),
846
-
657
+ // return snapshot if syncing from scratch or forcing after a failed attempt
847
658
  return_snapshot: (shouldForceSnapshot || !state.version).toString()
848
659
  }
849
660
  })
@@ -863,7 +674,7 @@ const makeChatsSocket = config => {
863
674
  }
864
675
  ]
865
676
  })
866
-
677
+ // extract from binary node
867
678
  const decoded = await (0, Utils_1.extractSyncdPatches)(result, config?.options)
868
679
  for (const key in decoded) {
869
680
  const name = key
@@ -882,7 +693,7 @@ const makeChatsSocket = config => {
882
693
  logger.info(`restored state of ${name} from snapshot to v${newState.version} with mutations`)
883
694
  await authState.keys.set({ 'app-state-sync-version': { [name]: newState } })
884
695
  }
885
-
696
+ // only process if there are syncd patches
886
697
  if (patches.length) {
887
698
  const { state: newState, mutationMap } = await (0, Utils_1.decodePatches)(
888
699
  name,
@@ -902,7 +713,7 @@ const makeChatsSocket = config => {
902
713
  if (hasMorePatches) {
903
714
  logger.info(`${name} has more patches...`)
904
715
  } else {
905
-
716
+ // collection is done with sync
906
717
  collectionsToHandle.delete(name)
907
718
  }
908
719
  } catch (error) {
@@ -944,15 +755,18 @@ const makeChatsSocket = config => {
944
755
  onMutation(globalMutationMap[key])
945
756
  }
946
757
  })
947
-
758
+ /**
759
+ * fetch the profile picture of a user/group
760
+ * type = "preview" for a low res picture
761
+ * type = "image" for the high res picture URL (adds query="url")
762
+ * type = "avatar" for the avatar variant (no query attr, confirmed from interop logs)
763
+ */
948
764
  const profilePictureUrl = async (jid, type = 'preview', timeoutMs) => {
949
-
950
-
951
- const picAttrs = { type, query: 'url' }
765
+ const picAttrs = type === 'image' ? { type, query: 'url' } : { type }
952
766
  const baseContent = [{ tag: 'picture', attrs: picAttrs }]
953
-
954
-
955
-
767
+ // WA Web only includes tctoken for user JIDs (not groups/newsletters)
768
+ // and never for own profile pic (Chat model for self has no tcToken).
769
+ // Including tctoken for own JID causes the server to never respond.
956
770
  const normalizedJid = (0, WABinary_1.jidNormalizedUser)(jid)
957
771
  const isUserJid = (0, WABinary_1.isPnUser)(normalizedJid) || (0, WABinary_1.isLidUser)(normalizedJid)
958
772
  const me = authState.creds.me
@@ -1007,7 +821,13 @@ const makeChatsSocket = config => {
1007
821
  const child = (0, WABinary_1.getBinaryNodeChild)(result, 'link_create')
1008
822
  return child?.attrs?.token
1009
823
  }
1010
-
824
+ /**
825
+ * Toggle the waiting room on an existing call link.
826
+ * Ported from WhatsApp Web's WASmaxVoipWaitingRoomToggleCallLinkRPC.
827
+ * @param {string} linkToken the call link token (from createCallLink)
828
+ * @param {boolean} enabled
829
+ * @param {'audio' | 'video'} [media]
830
+ */
1011
831
  const toggleCallLinkWaitingRoom = async (linkToken, enabled, media = 'audio') => {
1012
832
  const result = await query({
1013
833
  tag: 'call',
@@ -1059,120 +879,25 @@ const makeChatsSocket = config => {
1059
879
  })
1060
880
  }
1061
881
  }
1062
-
1063
- const storePrivacyTokens = async entries => {
1064
- if (!entries?.length) return
1065
- const write = {}
1066
- for (const { jid, privacyToken, privacyModeTs } of entries) {
1067
- if (!jid || !privacyToken?.length) continue
1068
- write[jid] = {
1069
- token: Buffer.isBuffer(privacyToken) ? privacyToken : Buffer.from(privacyToken),
1070
- ts: String(privacyModeTs || '')
1071
- }
1072
- }
1073
- if (Object.keys(write).length) {
1074
- await authState.keys.set({ 'privacy-token': write })
1075
- }
1076
- }
1077
-
1078
-
1079
- const executeUSyncQuery = async usyncQuery => {
1080
- const result = await sock.executeUSyncQuery(usyncQuery)
1081
- if (!result) return result
1082
-
1083
-
1084
- const privacyEntries = []
1085
- const blockedContacts = []
1086
- for (const entry of [...(result.list || []), ...(result.sideList || [])]) {
1087
- if (entry.privacy?.token) {
1088
- privacyEntries.push({
1089
- jid: entry.id,
1090
- privacyToken: entry.privacy.token,
1091
- privacyModeTs: entry.privacy.modeTs
1092
- })
1093
- }
1094
-
1095
- if (entry.isBlockedByContact) {
1096
- blockedContacts.push({ id: entry.id, isBlockedByContact: true })
1097
- }
1098
- }
1099
- if (privacyEntries.length) {
1100
- storePrivacyTokens(privacyEntries).catch(err => logger.warn({ err }, 'failed to store privacy tokens from usync'))
1101
- }
1102
- if (blockedContacts.length) {
1103
- ev.emit('contacts.update', blockedContacts)
1104
- }
1105
- return result
1106
- }
1107
-
1108
-
1109
- const presenceSubscribe = async (toJid, { presenceType, presenceName, groupJid } = {}) => {
1110
-
882
+ /**
883
+ * @param toJid the jid to subscribe to
884
+ * @param tcToken token for subscription, use if present
885
+ */
886
+ const presenceSubscribe = async toJid => {
887
+ // Only include tctoken for user JIDs — groups/newsletters don't use tctokens
1111
888
  const normalizedToJid = (0, WABinary_1.jidNormalizedUser)(toJid)
1112
889
  const isUserJid = (0, WABinary_1.isPnUser)(normalizedToJid) || (0, WABinary_1.isLidUser)(normalizedToJid)
1113
-
1114
-
1115
- const presenceAttrs = {
1116
- to: toJid,
1117
- id: generateMessageTag(),
1118
- type: 'subscribe'
1119
- }
1120
-
1121
-
1122
- const presenceContent = []
1123
-
1124
-
1125
- if (isUserJid) {
1126
- try {
1127
- const storageJid = await (0, tc_token_utils_1.resolveTcTokenJid)(normalizedToJid, getLIDForPN)
1128
- const tcTokenData = await authState.keys.get('tctoken', [storageJid])
1129
- const entry = tcTokenData?.[storageJid]
1130
- const tcTokenBuffer = entry?.token
1131
- if (tcTokenBuffer?.length && !(0, tc_token_utils_1.isTcTokenExpired)(entry?.timestamp)) {
1132
- presenceAttrs.tc_token = tcTokenBuffer.toString('base64')
1133
- }
1134
- } catch (e) {
1135
-
1136
- }
1137
-
1138
-
1139
-
1140
-
1141
- try {
1142
- const privTokenData = await authState.keys.get('privacy-token', [normalizedToJid])
1143
- const privEntry = privTokenData?.[normalizedToJid]
1144
- if (privEntry?.token?.length) {
1145
- const privTokenAttrs = {}
1146
- if (privEntry.ts) privTokenAttrs.t = privEntry.ts
1147
- presenceContent.push({
1148
- tag: 'privacy_token',
1149
- attrs: privTokenAttrs,
1150
- content: privEntry.token
1151
- })
1152
- }
1153
- } catch (e) {
1154
-
1155
- }
1156
- }
1157
-
1158
-
1159
- if (presenceType) {
1160
- presenceAttrs.presenceType = presenceType
1161
- }
1162
- if (presenceName) {
1163
- presenceAttrs.presenceName = presenceName
1164
- }
1165
-
1166
-
1167
- if (groupJid) {
1168
- presenceAttrs.context = 'group'
1169
- presenceAttrs.group_jid = (0, WABinary_1.jidNormalizedUser)(groupJid)
1170
- }
1171
-
890
+ const tcTokenContent = isUserJid
891
+ ? await (0, tc_token_utils_1.buildTcTokenFromJid)({ authState, jid: normalizedToJid, getLIDForPN })
892
+ : undefined
1172
893
  return sendNode({
1173
894
  tag: 'presence',
1174
- attrs: presenceAttrs,
1175
- content: presenceContent.length ? presenceContent : undefined
895
+ attrs: {
896
+ to: toJid,
897
+ id: generateMessageTag(),
898
+ type: 'subscribe'
899
+ },
900
+ content: tcTokenContent
1176
901
  })
1177
902
  }
1178
903
  const handlePresenceUpdate = ({ tag, attrs, content }) => {
@@ -1271,9 +996,9 @@ const makeChatsSocket = config => {
1271
996
  }
1272
997
  }
1273
998
  }
1274
-
999
+ /** sending non-abt props may fix QR scan fail if server expects */
1275
1000
  const fetchProps = async () => {
1276
-
1001
+ //TODO: implement both protocol 1 and protocol 2 prop fetching, specially for abKey for WM
1277
1002
  const resultNode = await query({
1278
1003
  tag: 'iq',
1279
1004
  attrs: {
@@ -1295,13 +1020,13 @@ const makeChatsSocket = config => {
1295
1020
  let props = {}
1296
1021
  if (propsNode) {
1297
1022
  if (propsNode.attrs?.hash) {
1298
-
1023
+ // on some clients, the hash is returning as undefined
1299
1024
  authState.creds.lastPropHash = propsNode?.attrs?.hash
1300
1025
  ev.emit('creds.update', authState.creds)
1301
1026
  }
1302
1027
  props = (0, WABinary_1.reduceBinaryNodeToDictionary)(propsNode, 'prop')
1303
1028
  }
1304
-
1029
+ // Extract protocol-relevant AB props (only the ones we need)
1305
1030
  const privacyTokenProp = props['10518'] ?? props['privacy_token_sending_on_all_1_on_1_messages']
1306
1031
  if (privacyTokenProp !== undefined) {
1307
1032
  serverProps.privacyTokenOn1to1 = privacyTokenProp === 'true' || privacyTokenProp === '1'
@@ -1317,12 +1042,18 @@ const makeChatsSocket = config => {
1317
1042
  logger.debug({ serverProps }, 'fetched props')
1318
1043
  return props
1319
1044
  }
1320
-
1045
+ /**
1046
+ * modify a chat -- mark unread, read etc.
1047
+ * lastMessages must be sorted in reverse chronologically
1048
+ * requires the last messages till the last message received; required for archive & unread
1049
+ */
1321
1050
  const chatModify = (mod, jid) => {
1322
1051
  const patch = (0, Utils_1.chatModificationToAppPatch)(mod, jid)
1323
1052
  return appPatch(patch)
1324
1053
  }
1325
-
1054
+ /**
1055
+ * Enable/Disable link preview privacy, not related to baileys link preview generation
1056
+ */
1326
1057
  const updateDisableLinkPreviewsPrivacy = isPreviewsDisabled => {
1327
1058
  return chatModify(
1328
1059
  {
@@ -1331,7 +1062,9 @@ const makeChatsSocket = config => {
1331
1062
  ''
1332
1063
  )
1333
1064
  }
1334
-
1065
+ /**
1066
+ * Star or Unstar a message
1067
+ */
1335
1068
  const star = (jid, messages, star) => {
1336
1069
  return chatModify(
1337
1070
  {
@@ -1343,7 +1076,9 @@ const makeChatsSocket = config => {
1343
1076
  jid
1344
1077
  )
1345
1078
  }
1346
-
1079
+ /**
1080
+ * Add or Edit Contact
1081
+ */
1347
1082
  const addOrEditContact = (jid, contact) => {
1348
1083
  return chatModify(
1349
1084
  {
@@ -1352,7 +1087,9 @@ const makeChatsSocket = config => {
1352
1087
  jid
1353
1088
  )
1354
1089
  }
1355
-
1090
+ /**
1091
+ * Remove Contact
1092
+ */
1356
1093
  const removeContact = jid => {
1357
1094
  return chatModify(
1358
1095
  {
@@ -1361,7 +1098,9 @@ const makeChatsSocket = config => {
1361
1098
  jid
1362
1099
  )
1363
1100
  }
1364
-
1101
+ /**
1102
+ * Adds label
1103
+ */
1365
1104
  const addLabel = (jid, labels) => {
1366
1105
  return chatModify(
1367
1106
  {
@@ -1372,7 +1111,9 @@ const makeChatsSocket = config => {
1372
1111
  jid
1373
1112
  )
1374
1113
  }
1375
-
1114
+ /**
1115
+ * Adds label for the chats
1116
+ */
1376
1117
  const addChatLabel = (jid, labelId) => {
1377
1118
  return chatModify(
1378
1119
  {
@@ -1383,7 +1124,9 @@ const makeChatsSocket = config => {
1383
1124
  jid
1384
1125
  )
1385
1126
  }
1386
-
1127
+ /**
1128
+ * Removes label for the chat
1129
+ */
1387
1130
  const removeChatLabel = (jid, labelId) => {
1388
1131
  return chatModify(
1389
1132
  {
@@ -1394,7 +1137,9 @@ const makeChatsSocket = config => {
1394
1137
  jid
1395
1138
  )
1396
1139
  }
1397
-
1140
+ /**
1141
+ * Adds label for the message
1142
+ */
1398
1143
  const addMessageLabel = (jid, messageId, labelId) => {
1399
1144
  return chatModify(
1400
1145
  {
@@ -1406,7 +1151,9 @@ const makeChatsSocket = config => {
1406
1151
  jid
1407
1152
  )
1408
1153
  }
1409
-
1154
+ /**
1155
+ * Removes label for the message
1156
+ */
1410
1157
  const removeMessageLabel = (jid, messageId, labelId) => {
1411
1158
  return chatModify(
1412
1159
  {
@@ -1418,7 +1165,9 @@ const makeChatsSocket = config => {
1418
1165
  jid
1419
1166
  )
1420
1167
  }
1421
-
1168
+ /**
1169
+ * Add or Edit Quick Reply
1170
+ */
1422
1171
  const addOrEditQuickReply = quickReply => {
1423
1172
  return chatModify(
1424
1173
  {
@@ -1427,7 +1176,9 @@ const makeChatsSocket = config => {
1427
1176
  ''
1428
1177
  )
1429
1178
  }
1430
-
1179
+ /**
1180
+ * Remove Quick Reply
1181
+ */
1431
1182
  const removeQuickReply = timestamp => {
1432
1183
  return chatModify(
1433
1184
  {
@@ -1436,49 +1187,34 @@ const makeChatsSocket = config => {
1436
1187
  ''
1437
1188
  )
1438
1189
  }
1439
-
1190
+ /**
1191
+ * Rename an AI chat thread
1192
+ */
1440
1193
  const renameAIThread = (jid, title) => {
1441
1194
  return chatModify({ aiThreadRename: { title } }, jid)
1442
1195
  }
1443
-
1196
+ /**
1197
+ * Pin or unpin a message in a thread/AI chat
1198
+ */
1444
1199
  const pinThreadMessage = (jid, messageId, pinned = true) => {
1445
1200
  return chatModify({ threadPin: { pinned, messageId } }, jid)
1446
1201
  }
1447
-
1202
+ /**
1203
+ * Toggle AI private processing (end-to-end encrypted AI processing)
1204
+ */
1448
1205
  const updatePrivateProcessingSetting = enabled => {
1449
1206
  return chatModify({ privateProcessingSetting: enabled ? 'enabled' : 'disabled' }, '')
1450
1207
  }
1451
-
1452
- const updateAIFeatures = (status = 'enabled', replyMode = 'suggestions') => {
1453
- const statusEnum =
1454
- require('../../WAProto/index.js').proto.SyncActionValue.MaibaAIFeaturesControlAction.MaibaAIFeatureStatus
1455
- const replyModeEnum =
1456
- require('../../WAProto/index.js').proto.SyncActionValue.MaibaAIFeaturesControlAction.MaibaAIReplyMode
1457
- const statusMap = {
1458
- enabled: statusEnum.ENABLED,
1459
- enabled_has_learning: statusEnum.ENABLED_HAS_LEARNING,
1460
- disabled: statusEnum.DISABLED
1461
- }
1462
- const replyModeMap = {
1463
- muted: replyModeEnum.MUTED,
1464
- ai_agent: replyModeEnum.AI_AGENT,
1465
- suggestions: replyModeEnum.SUGGESTIONS
1466
- }
1467
- return chatModify(
1468
- {
1469
- maibaAIFeatures: {
1470
- status: statusMap[status] ?? statusEnum.ENABLED,
1471
- replyMode: replyModeMap[replyMode] ?? replyModeEnum.SUGGESTIONS
1472
- }
1473
- },
1474
- ''
1475
- )
1476
- }
1477
-
1208
+ /**
1209
+ * Update bio/about privacy (who can see your About text)
1210
+ * @param value 'all' | 'contacts' | 'contact_blacklist' | 'none'
1211
+ */
1478
1212
  const updateBioPrivacy = async value => {
1479
1213
  await privacyQuery('about', value)
1480
1214
  }
1481
-
1215
+ /**
1216
+ * Block a bot JID
1217
+ */
1482
1218
  const blockBot = async botJid => {
1483
1219
  await query({
1484
1220
  tag: 'iq',
@@ -1495,63 +1231,71 @@ const makeChatsSocket = config => {
1495
1231
  ]
1496
1232
  })
1497
1233
  }
1498
-
1234
+ /**
1235
+ * Unblock a bot JID
1236
+ */
1499
1237
  const unblockBot = async botJid => {
1500
1238
  await updateBlockStatus(botJid, 'unblock')
1501
1239
  }
1502
-
1240
+ /**
1241
+ * Mute or unmute a contact's status updates
1242
+ */
1503
1243
  const muteContactStatus = (jid, muted = true) => {
1504
1244
  return chatModify({ muteStatus: { muted } }, jid)
1505
1245
  }
1506
-
1246
+ /**
1247
+ * Add or remove a contact from favorites
1248
+ */
1507
1249
  const toggleFavorite = (jid, isFavorite = true) => {
1508
1250
  return chatModify({ favorite: { isFavorite } }, jid)
1509
1251
  }
1510
-
1252
+ /**
1253
+ * Reorder labels
1254
+ * @param sortedLabelIds ordered array of label IDs
1255
+ */
1511
1256
  const reorderLabels = sortedLabelIds => {
1512
1257
  return chatModify({ reorderLabel: { sortedLabelIds } }, '')
1513
1258
  }
1514
-
1259
+ /**
1260
+ * Delete an individual call log entry
1261
+ */
1515
1262
  const deleteCallLog = (callId, jid = '') => {
1516
1263
  return chatModify({ deleteCallLog: { callId } }, jid)
1517
1264
  }
1518
-
1265
+ /**
1266
+ * Create or update a note/draft for a chat
1267
+ */
1519
1268
  const setChatNote = (jid, note) => {
1520
1269
  return chatModify({ noteEdit: { note } }, jid)
1521
1270
  }
1522
-
1271
+ /**
1272
+ * Delete the note/draft for a chat
1273
+ */
1523
1274
  const deleteChatNote = jid => {
1524
1275
  return chatModify({ noteEdit: { note: '', deleted: true } }, jid)
1525
1276
  }
1526
-
1277
+ /**
1278
+ * Explicitly mark a chat as unread (shows unread dot even if read)
1279
+ */
1527
1280
  const markChatAsUnread = (jid, lastMessages) => {
1528
1281
  return chatModify({ markAsUnread: true, lastMessages }, jid)
1529
1282
  }
1530
-
1283
+ /**
1284
+ * Set per-chat ephemeral message duration
1285
+ * @param duration seconds (0 = off, 86400 = 1d, 604800 = 7d, 7776000 = 90d)
1286
+ */
1531
1287
  const setChatEphemeral = (jid, duration) => {
1532
1288
  return chatModify({ setChatEphemeral: duration }, jid)
1533
1289
  }
1534
-
1290
+ /**
1291
+ * Silence a chat (mute without timestamp = permanent, or provide until timestamp)
1292
+ */
1535
1293
  const silenceChat = (jid, silent = true, until = null) => {
1536
1294
  return chatModify({ silenceChat: { silent, until } }, jid)
1537
1295
  }
1538
-
1539
- const clearChat = (jid, lastMessages) => {
1540
- return chatModify({ clear: true, lastMessages }, jid)
1541
- }
1542
-
1543
- const pinChat = (jid, pinned = true) => {
1544
- return chatModify({ pin: { pinned } }, jid)
1545
- }
1546
-
1547
- const starMessages = (jid, messageIds, starred = true) => {
1548
- return chatModify({ star: { messages: messageIds, star: starred } }, jid)
1549
- }
1550
-
1551
- const setQuickReply = (text, shortcut) => {
1552
- return chatModify({ quickReply: { text, shortcut } }, '')
1553
- }
1554
-
1296
+ /**
1297
+ * Fetch bot profiles for a list of JIDs using USyncBotProfileProtocol
1298
+ */
1555
1299
  const fetchBotProfiles = async jids => {
1556
1300
  const { USyncQuery, USyncUser, USyncBotProfileProtocol } = require('../WAUSync')
1557
1301
  const q = new USyncQuery()
@@ -1559,25 +1303,37 @@ const makeChatsSocket = config => {
1559
1303
  for (const jid of jids) {
1560
1304
  q.withUser(new USyncUser().withId(jid))
1561
1305
  }
1562
- const result = await executeUSyncQuery(q)
1306
+ const result = await sock.executeUSyncQuery(q)
1563
1307
  return result?.list || []
1564
1308
  }
1565
-
1309
+ /**
1310
+ * Lock or unlock a chat with an optional secret code
1311
+ */
1566
1312
  const updateChatLock = (jid, locked) => {
1567
1313
  return chatModify({ chatLock: { locked } }, jid)
1568
1314
  }
1569
-
1315
+ /**
1316
+ * Set a custom wallpaper for a chat
1317
+ * @param jid the chat JID
1318
+ * @param wallpaper wallpaper data or null to remove
1319
+ */
1570
1320
  const updateChatWallpaper = (jid, wallpaper) => {
1571
1321
  if (!wallpaper) {
1572
1322
  return chatModify({ wallpaper: { remove: true } }, jid)
1573
1323
  }
1574
1324
  return chatModify({ wallpaper }, jid)
1575
1325
  }
1576
-
1326
+ /**
1327
+ * Set media visibility (auto-download) for a chat
1328
+ * @param jid the chat JID
1329
+ * @param visibility 'default' | 'on' | 'off'
1330
+ */
1577
1331
  const updateChatMediaVisibility = (jid, visibility) => {
1578
1332
  return chatModify({ mediaVisibility: visibility }, jid)
1579
1333
  }
1580
-
1334
+ /**
1335
+ * Fetch details for a specific bot by JID
1336
+ */
1581
1337
  const getBotProfile = async botJid => {
1582
1338
  const resp = await query({
1583
1339
  tag: 'iq',
@@ -1602,16 +1358,15 @@ const makeChatsSocket = config => {
1602
1358
  description: botNode.attrs.description
1603
1359
  }
1604
1360
  }
1605
-
1606
- const fetchABProps = async (protocol = '1', hash = '', refreshId = null, group = null) => {
1607
- const propAttrs =
1608
- group != null
1609
- ? { group: String(group) }
1610
- : {
1611
- protocol,
1612
- ...(hash ? { hash } : {}),
1613
- ...(refreshId != null ? { refresh_id: String(refreshId) } : {})
1614
- }
1361
+ /**
1362
+ * Fetch AB-test (abt) props from server.
1363
+ * Mirrors ABPropsProtocolHelper — protocol 1 or 2, optional hash/refresh_id/group.
1364
+ */
1365
+ const fetchABProps = async (protocol = '2', hash = '', refreshId = null, group = null) => {
1366
+ const propAttrs = { protocol }
1367
+ if (hash) propAttrs.hash = hash
1368
+ if (refreshId != null) propAttrs.refresh_id = String(refreshId)
1369
+ if (group != null) propAttrs.group = String(group)
1615
1370
  const result = await query({
1616
1371
  tag: 'iq',
1617
1372
  attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'abt', type: 'get' },
@@ -1621,20 +1376,29 @@ const makeChatsSocket = config => {
1621
1376
  if (!propsNode) return {}
1622
1377
  return (0, WABinary_1.reduceBinaryNodeToDictionary)(propsNode, 'prop')
1623
1378
  }
1624
-
1625
- const removeCompanionDevice = async (jid, reason = 'user_initiated') => {
1379
+ /**
1380
+ * Remove a companion (linked) device from the account.
1381
+ * Mirrors CompanionDeviceRemovalJob — xmlns="md", child tag "remove-companion-device".
1382
+ * reason: "user_initiated" | "server_initiated" | any WA-defined reason string.
1383
+ */
1384
+ const removeCompanionDevice = async (keyIndex, reason = 'user_initiated') => {
1626
1385
  await query({
1627
1386
  tag: 'iq',
1628
1387
  attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'md', type: 'set' },
1629
1388
  content: [
1630
1389
  {
1631
1390
  tag: 'remove-companion-device',
1632
- attrs: { jid, reason }
1391
+ attrs: { platform: 'true', reason, id: String(keyIndex) }
1633
1392
  }
1634
1393
  ]
1635
1394
  })
1636
1395
  }
1637
-
1396
+ /**
1397
+ * Push an updated key-index-list to the server (multi-device key announcement).
1398
+ * Mirrors KeyIndexListJob — xmlns="md", child tag "key-index-list" with a binary proto body.
1399
+ * ts: unix timestamp seconds (string or number).
1400
+ * content: Buffer — serialized proto KeyIndexList.
1401
+ */
1638
1402
  const updateKeyIndexList = async (ts, content) => {
1639
1403
  await query({
1640
1404
  tag: 'iq',
@@ -1648,39 +1412,11 @@ const makeChatsSocket = config => {
1648
1412
  ]
1649
1413
  })
1650
1414
  }
1651
-
1652
- const sendKeyIndexList = async () => {
1653
- const account = authState.creds.account
1654
- const signedIdentityKey = authState.creds.signedIdentityKey
1655
- if (!account?.details || !signedIdentityKey?.private) {
1656
- logger.debug('no ADV account / signed identity key, skipping key-index-list')
1657
- return
1658
- }
1659
- const deviceIdentity = index_js_1.proto.ADVDeviceIdentity.decode(account.details)
1660
- const keyIndex = deviceIdentity.keyIndex || 0
1661
- const accountType = deviceIdentity.accountType ?? index_js_1.proto.ADVEncryptionType.E2EE
1662
- const isHosted = accountType === index_js_1.proto.ADVEncryptionType.HOSTED
1663
- const ts = Math.floor(Date.now() / 1000)
1664
- const details = index_js_1.proto.ADVKeyIndexList.encode({
1665
- rawId: deviceIdentity.rawId || 0,
1666
- timestamp: ts,
1667
- currentIndex: keyIndex,
1668
- validIndexes: [keyIndex],
1669
- accountType
1670
- }).finish()
1671
- const sigPrefix = isHosted
1672
- ? Defaults_1.WA_ADV_HOSTED_KEY_INDEX_LIST_SIG_PREFIX
1673
- : Defaults_1.WA_ADV_KEY_INDEX_LIST_SIG_PREFIX
1674
- const accountSignature = Utils_1.Curve.sign(signedIdentityKey.private, Buffer.concat([sigPrefix, details]))
1675
- const signedKeyIndexList = { details, accountSignature }
1676
- if (isHosted && account.accountSignatureKey?.length) {
1677
- signedKeyIndexList.accountSignatureKey = account.accountSignatureKey
1678
- }
1679
- const content = index_js_1.proto.ADVSignedKeyIndexList.encode(signedKeyIndexList).finish()
1680
- await updateKeyIndexList(ts, Buffer.from(content))
1681
- logger.info({ ts, keyIndex }, 'sent key-index-list')
1682
- }
1683
-
1415
+ /**
1416
+ * Request a media upload connection token from the server.
1417
+ * Mirrors MediaConnFetcher — xmlns="w:m", type="set", optional last_id.
1418
+ * Returns the raw media_conn node.
1419
+ */
1684
1420
  const fetchMediaConn = async (lastId = null) => {
1685
1421
  const attrs = {}
1686
1422
  if (lastId != null) attrs.last_id = String(lastId)
@@ -1691,7 +1427,10 @@ const makeChatsSocket = config => {
1691
1427
  })
1692
1428
  return (0, WABinary_1.getBinaryNodeChild)(result, 'media_conn') || null
1693
1429
  }
1694
-
1430
+ /**
1431
+ * Delete a broadcast list by ID.
1432
+ * Mirrors BroadcastListDeleteJob — xmlns="w:b", wraps <delete><list id="..."/></delete>.
1433
+ */
1695
1434
  const deleteBroadcastList = async listId => {
1696
1435
  await query({
1697
1436
  tag: 'iq',
@@ -1705,7 +1444,11 @@ const makeChatsSocket = config => {
1705
1444
  ]
1706
1445
  })
1707
1446
  }
1708
-
1447
+ /**
1448
+ * Fetch a QR code from the server (e.g. for linked-device linking).
1449
+ * Mirrors QRCodeFetcher — xmlns="w:qr", type="get".
1450
+ * addressingMode: "lid" | undefined — set to "lid" for LID-addressed QR.
1451
+ */
1709
1452
  const fetchQRCode = async (code, addressingMode = null) => {
1710
1453
  const attrs = { code }
1711
1454
  if (addressingMode) attrs.addressing_mode = addressingMode
@@ -1716,13 +1459,15 @@ const makeChatsSocket = config => {
1716
1459
  })
1717
1460
  return (0, WABinary_1.getBinaryNodeChild)(result, 'qr') || null
1718
1461
  }
1719
-
1462
+ /**
1463
+ * Confirm or deny a device-logout request from the server.
1464
+ * Mirrors AccountDefenceDeviceLogoutJob — xmlns="w:account_defence", smax_id=87.
1465
+ * approve: true to confirm the logout, false to deny.
1466
+ */
1720
1467
  const confirmDeviceLogout = async (id, approve = true) => {
1721
1468
  await query({
1722
1469
  tag: 'iq',
1723
1470
  attrs: {
1724
-
1725
- id: String(id),
1726
1471
  to: WABinary_1.S_WHATSAPP_NET,
1727
1472
  xmlns: 'w:account_defence',
1728
1473
  type: 'set',
@@ -1736,78 +1481,14 @@ const makeChatsSocket = config => {
1736
1481
  ]
1737
1482
  })
1738
1483
  }
1739
-
1484
+ /**
1485
+ * queries need to be fired on connection open
1486
+ * help ensure parity with WA Web
1487
+ * */
1740
1488
  const executeInitQueries = async () => {
1741
-
1742
-
1743
- const shouldInitInterop = authState.creds.interopEnabled !== false
1744
- const [, , , abProps] = await Promise.all([
1745
- fetchProps(),
1746
- fetchBlocklist(),
1747
- fetchPrivacySettings(),
1748
- fetchABProps(),
1749
- ...(shouldInitInterop ? [initInterop()] : [])
1750
- ])
1751
- const INTEROP_FLAGS = ['stella_interop_enabled', 'stella_ios_enabled']
1752
- for (const flag of INTEROP_FLAGS) {
1753
- if (flag in abProps) {
1754
- const enabled = abProps[flag] === 'true' || abProps[flag] === '1'
1755
- ev.emit('interop.feature-update', { feature: flag, enabled })
1756
- }
1757
- }
1758
-
1759
- const abCredsUpdate = {}
1760
- if ('stella_interop_enabled' in abProps) {
1761
- abCredsUpdate.interopEnabled =
1762
- abProps['stella_interop_enabled'] === 'true' || abProps['stella_interop_enabled'] === '1'
1763
- }
1764
- if ('stella_ios_enabled' in abProps) {
1765
- abCredsUpdate.interopIosEnabled =
1766
- abProps['stella_ios_enabled'] === 'true' || abProps['stella_ios_enabled'] === '1'
1767
- }
1768
- if ('md_privacy_v2' in abProps) {
1769
- abCredsUpdate.mdPrivacyV2 = abProps['md_privacy_v2'] === 'true' || abProps['md_privacy_v2'] === '1'
1770
- }
1771
-
1772
- const MEDIA_AB_PROPS = [
1773
- 'hd_image_dual_upload',
1774
- 'hd_video_dual_upload',
1775
- 'hevc_video_dual_upload',
1776
- 'partial_pjpeg_enabled',
1777
- 'multi_scan_pjpeg',
1778
- 'media_poll'
1779
- ]
1780
- const mediaAbProps = {}
1781
- for (const prop of MEDIA_AB_PROPS) {
1782
- if (prop in abProps) {
1783
- mediaAbProps[prop] = abProps[prop] === 'true' || abProps[prop] === '1'
1784
- }
1785
- }
1786
- if (Object.keys(mediaAbProps).length) {
1787
- abCredsUpdate.mediaAbProps = { ...(authState.creds.mediaAbProps || {}), ...mediaAbProps }
1788
- }
1789
- if (Object.keys(abCredsUpdate).length) {
1790
- ev.emit('creds.update', abCredsUpdate)
1791
- }
1489
+ await Promise.all([fetchProps(), fetchBlocklist(), fetchPrivacySettings(), initInterop()])
1792
1490
  }
1793
1491
  const upsertMessage = ev.createBufferedFunction(async (msg, type) => {
1794
-
1795
- if (msg.newsletter && msg.newsletter_server_id && msg.key?.id) {
1796
- _nlServerIdCache.set(msg.key.id, msg.newsletter_server_id)
1797
- }
1798
-
1799
- if (msg.message) {
1800
- const ci = Object.values(msg.message).find(v => v?.contextInfo)?.contextInfo
1801
- if (ci?.stanzaId && (0, WABinary_1.isJidNewsletter)(ci.remoteJid)) {
1802
- const sid = _nlServerIdCache.get(ci.stanzaId)
1803
- if (sid != null) msg.quotedNewsletterServerId = sid
1804
- }
1805
- }
1806
- if (msg.message?.secretEncryptedMessage || msg.message?.editedMessage?.message?.secretEncryptedMessage) {
1807
-
1808
-
1809
- await unwrapSecretEncryptedMessage(msg, { creds: authState.creds, getMessage, logger })
1810
- }
1811
1492
  ev.emit('messages.upsert', { messages: [msg], type })
1812
1493
  if (!!msg.pushName) {
1813
1494
  let jid = msg.key.fromMe ? authState.creds.me.id : msg.key.participant || msg.key.remoteJid
@@ -1815,7 +1496,7 @@ const makeChatsSocket = config => {
1815
1496
  if (!msg.key.fromMe) {
1816
1497
  ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }])
1817
1498
  }
1818
-
1499
+ // update our pushname too
1819
1500
  if (msg.key.fromMe && msg.pushName && authState.creds.me?.name !== msg.pushName) {
1820
1501
  ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } })
1821
1502
  }
@@ -1826,7 +1507,7 @@ const makeChatsSocket = config => {
1826
1507
  : false
1827
1508
  if (historyMsg && shouldProcessHistoryMsg) {
1828
1509
  const syncType = historyMsg.syncType
1829
-
1510
+ // INITIAL_BOOTSTRAP — fire immediately, no progress check
1830
1511
  if (
1831
1512
  syncType === index_js_1.proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP &&
1832
1513
  !historySyncStatus.initialBootstrapComplete
@@ -1838,7 +1519,7 @@ const makeChatsSocket = config => {
1838
1519
  explicit: true
1839
1520
  })
1840
1521
  }
1841
-
1522
+ // RECENT with progress === 100 — explicit completion
1842
1523
  if (
1843
1524
  syncType === index_js_1.proto.HistorySync.HistorySyncType.RECENT &&
1844
1525
  historyMsg.progress === 100 &&
@@ -1853,7 +1534,7 @@ const makeChatsSocket = config => {
1853
1534
  explicit: true
1854
1535
  })
1855
1536
  }
1856
-
1537
+ // Reset 120s paused timeout on any RECENT chunk
1857
1538
  if (syncType === index_js_1.proto.HistorySync.HistorySyncType.RECENT && !historySyncStatus.recentSyncComplete) {
1858
1539
  clearTimeout(historySyncPausedTimeout)
1859
1540
  historySyncPausedTimeout = setTimeout(() => {
@@ -1869,7 +1550,7 @@ const makeChatsSocket = config => {
1869
1550
  }, Defaults_1.HISTORY_SYNC_PAUSED_TIMEOUT_MS)
1870
1551
  }
1871
1552
  }
1872
-
1553
+ // State machine: decide on sync and flush
1873
1554
  if (historyMsg && syncState === State_1.SyncState.AwaitingInitialSync) {
1874
1555
  if (awaitingSyncTimeout) {
1875
1556
  clearTimeout(awaitingSyncTimeout)
@@ -1878,7 +1559,7 @@ const makeChatsSocket = config => {
1878
1559
  if (shouldProcessHistoryMsg) {
1879
1560
  syncState = State_1.SyncState.Syncing
1880
1561
  logger.info('Transitioned to Syncing state')
1881
-
1562
+ // Let doAppStateSync handle the final flush after it's done
1882
1563
  } else {
1883
1564
  syncState = State_1.SyncState.Online
1884
1565
  logger.info('History sync skipped, transitioning to Online state and flushing buffer')
@@ -1887,11 +1568,11 @@ const makeChatsSocket = config => {
1887
1568
  }
1888
1569
  const doAppStateSync = async () => {
1889
1570
  if (syncState === State_1.SyncState.Syncing) {
1890
-
1571
+ // All collections will be synced, so clear any blocked ones
1891
1572
  blockedCollections.clear()
1892
1573
  logger.info('Doing app state sync')
1893
1574
  await resyncAppState(Types_1.ALL_WA_PATCH_NAMES, true)
1894
-
1575
+ // Sync is complete, go online and flush everything
1895
1576
  syncState = State_1.SyncState.Online
1896
1577
  logger.info('App state sync complete, transitioning to Online state and flushing buffer')
1897
1578
  ev.flush()
@@ -1917,7 +1598,7 @@ const makeChatsSocket = config => {
1917
1598
  getMessage
1918
1599
  })
1919
1600
  ])
1920
-
1601
+ // If the app state key arrives and we are waiting to sync, trigger the sync now.
1921
1602
  if (msg.message?.protocolMessage?.appStateSyncKeyShare && syncState === State_1.SyncState.Syncing) {
1922
1603
  logger.info('App state sync key arrived, triggering app state sync')
1923
1604
  await doAppStateSync()
@@ -1925,20 +1606,6 @@ const makeChatsSocket = config => {
1925
1606
  })
1926
1607
  ws.on('CB:presence', handlePresenceUpdate)
1927
1608
  ws.on('CB:chatstate', handlePresenceUpdate)
1928
-
1929
-
1930
-
1931
- ws.on('CB:iq,xmlns:w:account_defence', async node => {
1932
- const deviceLogout = (0, WABinary_1.getBinaryNodeChild)(node, 'device_logout')
1933
- if (!deviceLogout) return
1934
- const id = node.attrs.id
1935
- logger.info({ id }, 'received account_defence device_logout challenge, approving')
1936
- try {
1937
- await confirmDeviceLogout(id, true)
1938
- } catch (error) {
1939
- onUnexpectedError(error, 'account_defence device_logout approve')
1940
- }
1941
- })
1942
1609
  ws.on('CB:ib,,dirty', async node => {
1943
1610
  const { attrs } = (0, WABinary_1.getBinaryNodeChild)(node, 'dirty')
1944
1611
  const type = attrs.type
@@ -1954,7 +1621,7 @@ const makeChatsSocket = config => {
1954
1621
  }
1955
1622
  break
1956
1623
  case 'groups':
1957
-
1624
+ // handled in groups.ts
1958
1625
  break
1959
1626
  default:
1960
1627
  logger.info({ node }, 'received unknown sync')
@@ -1974,9 +1641,6 @@ const makeChatsSocket = config => {
1974
1641
  sendPresenceUpdate(markOnlineOnConnect ? 'available' : 'unavailable').catch(error =>
1975
1642
  onUnexpectedError(error, 'presence update requests')
1976
1643
  )
1977
-
1978
-
1979
- sendKeyIndexList().catch(error => onUnexpectedError(error, 'send key-index-list'))
1980
1644
  }
1981
1645
  if (!receivedPendingNotifications || syncState !== State_1.SyncState.Connecting) {
1982
1646
  return
@@ -1999,9 +1663,9 @@ const makeChatsSocket = config => {
1999
1663
  setTimeout(() => ev.flush(), 0)
2000
1664
  return
2001
1665
  }
2002
-
2003
-
2004
-
1666
+ // On reconnection (accountSyncCounter > 0), the server does not push
1667
+ // history sync notifications — the device already has its data.
1668
+ // Skip the 20s wait and go online immediately.
2005
1669
  if (authState.creds.accountSyncCounter > 0) {
2006
1670
  logger.info('Reconnection with existing sync data, skipping history sync wait. Transitioning to Online.')
2007
1671
  syncState = State_1.SyncState.Online
@@ -2017,18 +1681,18 @@ const makeChatsSocket = config => {
2017
1681
  logger.warn('Timeout in AwaitingInitialSync, forcing state to Online and flushing buffer')
2018
1682
  syncState = State_1.SyncState.Online
2019
1683
  ev.flush()
2020
-
1684
+ // Increment so subsequent reconnections skip the 20s wait.
2021
1685
  const accountSyncCounter = (authState.creds.accountSyncCounter || 0) + 1
2022
1686
  ev.emit('creds.update', { accountSyncCounter })
2023
1687
  }
2024
1688
  }, 20000)
2025
1689
  })
2026
-
1690
+ // When an app state sync key arrives and there are collections blocked on a missing key, re-sync them.
2027
1691
  ev.on('creds.update', ({ myAppStateKeyId }) => {
2028
1692
  if (!myAppStateKeyId || blockedCollections.size === 0) {
2029
1693
  return
2030
1694
  }
2031
-
1695
+ // If we're in the middle of a full sync, doAppStateSync handles all collections
2032
1696
  if (syncState === State_1.SyncState.Syncing) {
2033
1697
  blockedCollections.clear()
2034
1698
  return
@@ -2054,12 +1718,9 @@ const makeChatsSocket = config => {
2054
1718
  getChatBlockingStatus,
2055
1719
  updateChatBlockingStatus,
2056
1720
  getUserDisclosures,
2057
- acceptTosNotice,
2058
- reportSpam,
2059
1721
  getOptOutList,
2060
1722
  getPushConfig,
2061
1723
  setPushConfig,
2062
- signPrivateCredential,
2063
1724
  messageMutex,
2064
1725
  receiptMutex,
2065
1726
  appStatePatchMutex,
@@ -2108,7 +1769,6 @@ const makeChatsSocket = config => {
2108
1769
  renameAIThread,
2109
1770
  pinThreadMessage,
2110
1771
  updatePrivateProcessingSetting,
2111
- updateAIFeatures,
2112
1772
  updateBioPrivacy,
2113
1773
  blockBot,
2114
1774
  unblockBot,
@@ -2122,10 +1782,6 @@ const makeChatsSocket = config => {
2122
1782
  markChatAsUnread,
2123
1783
  setChatEphemeral,
2124
1784
  silenceChat,
2125
- clearChat,
2126
- pinChat,
2127
- starMessages,
2128
- setQuickReply,
2129
1785
  fetchBotProfiles,
2130
1786
  updateChatLock,
2131
1787
  updateChatWallpaper,
@@ -2154,12 +1810,7 @@ const makeChatsSocket = config => {
2154
1810
  fetchMediaConn,
2155
1811
  deleteBroadcastList,
2156
1812
  fetchQRCode,
2157
- confirmDeviceLogout,
2158
- updateKeyIndexList,
2159
- sendKeyIndexList,
2160
- storePrivacyTokens,
2161
- executeUSyncQuery,
2162
- newsletterServerIdCache: _nlServerIdCache
1813
+ confirmDeviceLogout
2163
1814
  }
2164
1815
  }
2165
1816
  exports.makeChatsSocket = makeChatsSocket