@nexustechpro/baileys 2.0.5 → 2.0.6

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.
@@ -1,16 +1,60 @@
1
- import { Boom } from "@hapi/boom"
2
- import { randomBytes } from "crypto"
3
- import { URL } from "url"
4
- import { promisify } from "util"
5
- import { proto } from "../../WAProto/index.js"
6
- import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, MIN_UPLOAD_INTERVAL, NOISE_WA_HEADER, UPLOAD_TIMEOUT, BATCH_SIZE, TimeMs } from "../Defaults/index.js"
7
- import { DisconnectReason } from "../Types/index.js"
8
- import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, bytesToCrockford, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getErrorCodeFromStreamError, getNextPreKeysNode, makeEventBuffer, makeNoiseHandler, promiseTimeout, signedKeyPair, xmppSignedPreKey } from "../Utils/index.js"
9
- import { getPlatformId, migrateIndexKey } from "../Utils/index.js"
10
- import { assertNodeErrorFree, binaryNodeToString, encodeBinaryNode, getBinaryNodeChild, getBinaryNodeChildren, isLidUser, jidDecode, jidEncode, S_WHATSAPP_NET } from "../WABinary/index.js"
11
- import { BinaryInfo } from "../WAM/BinaryInfo.js"
12
- import { USyncQuery, USyncUser } from "../WAUSync/index.js"
13
- import { WebSocketClient } from "./Client/index.js"
1
+ import { Boom } from '@hapi/boom'
2
+ import { randomBytes } from 'crypto'
3
+ import { URL } from 'url'
4
+ import { promisify } from 'util'
5
+ import { proto } from '../../WAProto/index.js'
6
+ import {
7
+ DEF_CALLBACK_PREFIX,
8
+ DEF_TAG_PREFIX,
9
+ INITIAL_PREKEY_COUNT,
10
+ MIN_PREKEY_COUNT,
11
+ MIN_UPLOAD_INTERVAL,
12
+ NOISE_WA_HEADER,
13
+ UPLOAD_TIMEOUT,
14
+ BATCH_SIZE,
15
+ TimeMs
16
+ } from '../Defaults/index.js'
17
+ import { QueryIds, ReachoutTimelockEnforcementType } from '../Types/index.js'
18
+ import { DisconnectReason, XWAPaths } from '../Types/index.js'
19
+ import {
20
+ addTransactionCapability,
21
+ aesEncryptCTR,
22
+ bindWaitForConnectionUpdate,
23
+ buildPairingQRData,
24
+ bytesToCrockford,
25
+ configureSuccessfulPairing,
26
+ Curve,
27
+ derivePairingCodeKey,
28
+ generateLoginNode,
29
+ generateMdTagPrefix,
30
+ generateRegistrationNode,
31
+ getCodeFromWSError,
32
+ getCompanionPlatformId,
33
+ getErrorCodeFromStreamError,
34
+ getNextPreKeysNode,
35
+ makeEventBuffer,
36
+ makeNoiseHandler,
37
+ promiseTimeout,
38
+ signedKeyPair,
39
+ xmppSignedPreKey
40
+ } from '../Utils/index.js'
41
+ import { getPlatformId, migrateIndexKey } from '../Utils/index.js'
42
+ import {
43
+ assertNodeErrorFree,
44
+ binaryNodeToString,
45
+ encodeBinaryNode,
46
+ getAllBinaryNodeChildren,
47
+ getBinaryNodeChild,
48
+ getBinaryNodeChildren,
49
+ isLidUser,
50
+ jidDecode,
51
+ jidEncode,
52
+ S_WHATSAPP_NET
53
+ } from '../WABinary/index.js'
54
+ import { BinaryInfo } from '../WAM/BinaryInfo.js'
55
+ import { USyncQuery, USyncUser } from '../WAUSync/index.js'
56
+ import { WebSocketClient } from './Client/index.js'
57
+ import { executeWMexQuery } from './mex.js'
14
58
 
15
59
  // ─── Module-scope helpers ──────────────────────────────────────────────────────
16
60
 
@@ -43,7 +87,12 @@ export const makeSocket = (config) => {
43
87
  makeSignalRepository
44
88
  } = config
45
89
 
46
- if (printQRInTerminal) logger?.warn("printQRInTerminal deprecated")
90
+ if (printQRInTerminal) {
91
+ logger?.warn(
92
+ {},
93
+ '⚠️ printQRInTerminal is deprecated. Listen to connection.update and handle QR yourself.'
94
+ )
95
+ }
47
96
 
48
97
  const url = typeof waWebSocketUrl === 'string' ? new URL(waWebSocketUrl) : waWebSocketUrl
49
98
 
@@ -73,6 +122,7 @@ export const makeSocket = (config) => {
73
122
  const uqTagId = generateMdTagPrefix()
74
123
  const sendPromise = promisify(ws.send)
75
124
 
125
+ // State
76
126
  let epoch = 1
77
127
  let lastDateRecv
78
128
  let lastUploadTime = 0
@@ -81,6 +131,10 @@ export const makeSocket = (config) => {
81
131
  let keepAliveReq
82
132
  let qrTimer
83
133
  let serverTimeOffsetMs = 0
134
+ let didStartBuffer = false
135
+
136
+ /** Socket end handlers — registered via registerSocketEndHandler() */
137
+ const socketEndHandlers = []
84
138
 
85
139
  const generateMessageTag = () => `${uqTagId}${epoch++}`
86
140
 
@@ -153,7 +207,13 @@ export const makeSocket = (config) => {
153
207
  attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'usync' },
154
208
  content: [{
155
209
  tag: 'usync',
156
- attrs: { context: usyncQuery.context, mode: usyncQuery.mode, sid: generateMessageTag(), last: 'true', index: '0' },
210
+ attrs: {
211
+ context: usyncQuery.context,
212
+ mode: usyncQuery.mode,
213
+ sid: generateMessageTag(),
214
+ last: 'true',
215
+ index: '0'
216
+ },
157
217
  content: [
158
218
  { tag: 'query', attrs: {}, content: usyncQuery.protocols.map((a) => a.getQueryElement()) },
159
219
  { tag: 'list', attrs: {}, content: userNodes }
@@ -166,14 +226,30 @@ export const makeSocket = (config) => {
166
226
  async function pnFromLIDUSync(jids) {
167
227
  const usyncQuery = new USyncQuery().withLIDProtocol().withContext('background')
168
228
  for (const jid of jids) {
169
- if (!isLidUser(jid)) usyncQuery.withUser(new USyncUser().withId(jid))
170
- else logger?.warn('LID user found in LID fetch call')
229
+ if (isLidUser(jid)) { logger?.warn('LID user found in LID fetch call'); continue }
230
+ usyncQuery.withUser(new USyncUser().withId(jid))
171
231
  }
172
232
  if (usyncQuery.users.length === 0) return []
173
233
  const results = await executeUSyncQuery(usyncQuery)
174
234
  return results ? results.list.filter((a) => !!a.lid).map(({ lid, id }) => ({ pn: id, lid })) : []
175
235
  }
176
236
 
237
+ const onWhatsApp = async (...phoneNumbers) => {
238
+ let usyncQuery = new USyncQuery()
239
+ let contactEnabled = false
240
+ for (const jid of phoneNumbers) {
241
+ if (isLidUser(jid)) { logger?.warn('LIDs are not supported with onWhatsApp'); continue }
242
+ if (!contactEnabled) { contactEnabled = true; usyncQuery = usyncQuery.withContactProtocol() }
243
+ const phone = `+${jid.replace('+', '').split('@')[0]?.split(':')[0]}`
244
+ usyncQuery.withUser(new USyncUser().withPhone(phone))
245
+ }
246
+ if (usyncQuery.users.length === 0) return []
247
+ const results = await executeUSyncQuery(usyncQuery)
248
+ return results
249
+ ? results.list.filter((a) => !!a.contact).map(({ contact, id }) => ({ jid: id, exists: contact }))
250
+ : []
251
+ }
252
+
177
253
  // ─── Pre-keys ───────────────────────────────────────────────────────────────
178
254
 
179
255
  const getAvailablePreKeysOnServer = async () => {
@@ -185,11 +261,24 @@ export const makeSocket = (config) => {
185
261
  return +getBinaryNodeChild(result, 'count').attrs.value
186
262
  }
187
263
 
264
+ /**
265
+ * Verify that our current pre-key actually exists in local key storage.
266
+ * Catches the case where the server still has keys but our local store is missing them.
267
+ */
268
+ const verifyCurrentPreKeyExists = async () => {
269
+ const currentPreKeyId = creds.nextPreKeyId - 1
270
+ if (currentPreKeyId <= 0) return { exists: false, currentPreKeyId: 0 }
271
+ const preKeys = await keys.get('pre-key', [currentPreKeyId.toString()])
272
+ return { exists: !!preKeys[currentPreKeyId.toString()], currentPreKeyId }
273
+ }
274
+
188
275
  const uploadPreKeys = async (count = MIN_PREKEY_COUNT, retryCount = 0) => {
276
+ // Rate-limit guard — only on the first attempt, not retries
189
277
  if (retryCount === 0 && Date.now() - lastUploadTime < MIN_UPLOAD_INTERVAL) {
190
- logger.debug(`Skipping upload, only ${Date.now() - lastUploadTime}ms since last`)
278
+ logger.debug(`Skipping upload only ${Date.now() - lastUploadTime}ms since last upload`)
191
279
  return
192
280
  }
281
+ // Dedup: if an upload is already in-flight, wait for it
193
282
  if (uploadPreKeysPromise) {
194
283
  logger.debug('Pre-key upload in progress, waiting...')
195
284
  await uploadPreKeysPromise
@@ -197,6 +286,7 @@ export const makeSocket = (config) => {
197
286
  }
198
287
  const uploadLogic = async () => {
199
288
  logger.info({ count, retryCount }, 'Uploading pre-keys')
289
+ // Generate keys inside a transaction to prevent ID collisions on retry
200
290
  const node = await keys.transaction(async () => {
201
291
  const { update, node } = await getNextPreKeysNode({ creds, keys }, count)
202
292
  ev.emit('creds.update', update)
@@ -230,15 +320,23 @@ export const makeSocket = (config) => {
230
320
  try {
231
321
  const preKeyCount = await getAvailablePreKeysOnServer()
232
322
  logger.info(`${preKeyCount} pre-keys found on server`)
233
- if (preKeyCount < MIN_PREKEY_COUNT) {
234
- const uploadCount = INITIAL_PREKEY_COUNT - preKeyCount
235
- logger.info(`Server pre-key count low (${preKeyCount}), uploading ${uploadCount}`)
323
+ const { exists: currentPreKeyExists, currentPreKeyId } = await verifyCurrentPreKeyExists()
324
+ logger.info(`Current prekey ID: ${currentPreKeyId}, exists in storage: ${currentPreKeyExists}`)
325
+ const lowServerCount = preKeyCount <= MIN_PREKEY_COUNT
326
+ const missingCurrentPreKey = !currentPreKeyExists && currentPreKeyId > 0
327
+ if (lowServerCount || missingCurrentPreKey) {
328
+ const reasons = []
329
+ if (lowServerCount) reasons.push(`server count low (${preKeyCount})`)
330
+ if (missingCurrentPreKey) reasons.push(`current prekey ${currentPreKeyId} missing from storage`)
331
+ logger.info(`Uploading PreKeys due to: ${reasons.join(', ')}`)
332
+ const uploadCount = preKeyCount === 0 ? INITIAL_PREKEY_COUNT : MIN_PREKEY_COUNT
236
333
  await uploadPreKeys(uploadCount)
237
334
  } else {
238
- logger.info(`✅ PreKey validation passed - Server: ${preKeyCount} pre-keys`)
335
+ logger.info(`✅ PreKey validation passed Server: ${preKeyCount}, prekey ${currentPreKeyId} exists`)
239
336
  }
240
337
  } catch (error) {
241
338
  logger.error({ error }, 'Failed to check/upload pre-keys during init')
339
+ // Non-fatal — allow connection to continue
242
340
  }
243
341
  }
244
342
 
@@ -285,7 +383,7 @@ export const makeSocket = (config) => {
285
383
  const parsed = Number(tValue)
286
384
  if (Number.isNaN(parsed) || parsed <= 0) return
287
385
  serverTimeOffsetMs = parsed * 1000 - Date.now()
288
- logger.debug({ offset: serverTimeOffsetMs }, 'calculated server time offset')
386
+ logger.debug({ offset: serverTimeOffsetMs }, 'Calculated server time offset')
289
387
  }
290
388
 
291
389
  // ─── Unified session telemetry ───────────────────────────────────────────────
@@ -305,14 +403,65 @@ export const makeSocket = (config) => {
305
403
  content: [{ tag: 'unified_session', attrs: { id: getUnifiedSessionId() } }]
306
404
  })
307
405
  } catch (error) {
308
- logger.debug({ error }, 'failed to send unified_session telemetry')
406
+ logger.debug({ error }, 'Failed to send unified_session telemetry')
407
+ }
408
+ }
409
+
410
+ // ─── WAM buffer ─────────────────────────────────────────────────────────────
411
+
412
+ const sendWAMBuffer = (wamBuffer) =>
413
+ query({
414
+ tag: 'iq',
415
+ attrs: { to: S_WHATSAPP_NET, id: generateMessageTag(), xmlns: 'w:stats' },
416
+ content: [{ tag: 'add', attrs: { t: Math.round(Date.now() / 1000) + '' }, content: wamBuffer }]
417
+ })
418
+
419
+ // ─── WMex queries ────────────────────────────────────────────────────────────
420
+
421
+ /**
422
+ * Fetches account restriction / reachout timelock status.
423
+ */
424
+ const fetchAccountReachoutTimelock = async () => {
425
+ const queryResult = await executeWMexQuery(
426
+ {},
427
+ QueryIds.REACHOUT_TIMELOCK,
428
+ XWAPaths.xwa2_fetch_account_reachout_timelock,
429
+ query,
430
+ generateMessageTag
431
+ )
432
+ const result = {
433
+ isActive: !!queryResult?.is_active,
434
+ timeEnforcementEnds:
435
+ queryResult?.time_enforcement_ends && queryResult.time_enforcement_ends !== '0'
436
+ ? new Date(parseInt(queryResult.time_enforcement_ends, 10) * 1000)
437
+ : undefined,
438
+ enforcementType: queryResult?.enforcement_type ?? ReachoutTimelockEnforcementType.DEFAULT
309
439
  }
440
+ ev.emit('connection.update', { reachoutTimeLock: result })
441
+ return result
310
442
  }
311
443
 
444
+ /**
445
+ * Fetches new-chat message cap quota and usage.
446
+ */
447
+ const fetchNewChatMessageCap = async () =>
448
+ executeWMexQuery(
449
+ { input: { type: 'INDIVIDUAL_NEW_CHAT_MSG' } },
450
+ QueryIds.MESSAGE_CAPPING_INFO,
451
+ XWAPaths.xwa2_message_capping_info,
452
+ query,
453
+ generateMessageTag
454
+ )
455
+
312
456
  // ─── Connection lifecycle ────────────────────────────────────────────────────
313
457
 
314
458
  const onUnexpectedError = (err, msg) => {
315
- logger.error({ err }, `unexpected error in '${msg}'`)
459
+ const isClosed = err?.message?.includes('Connection Closed') || err?.output?.statusCode === 428
460
+ if (isClosed) {
461
+ logger.debug({ msg: err?.message }, `Connection closed during '${msg}'`)
462
+ } else {
463
+ logger.error({ err }, `unexpected error in '${msg}'`)
464
+ }
316
465
  }
317
466
 
318
467
  const awaitNextMessage = async (sendMsg) => {
@@ -335,7 +484,7 @@ export const makeSocket = (config) => {
335
484
  }
336
485
 
337
486
  const validateConnection = async () => {
338
- let helloMsg = proto.HandshakeMessage.fromObject({ clientHello: { ephemeral: ephemeralKeyPair.public } })
487
+ const helloMsg = proto.HandshakeMessage.fromObject({ clientHello: { ephemeral: ephemeralKeyPair.public } })
339
488
  logger.info({ browser, helloMsg }, 'Connected to WhatsApp')
340
489
  const init = proto.HandshakeMessage.encode(helloMsg).finish()
341
490
  const result = await awaitNextMessage(init)
@@ -346,9 +495,7 @@ export const makeSocket = (config) => {
346
495
  logger.info({ node }, !creds.me ? 'Attempting registration...' : 'Logging in...')
347
496
  const payloadEnc = noise.encrypt(proto.ClientPayload.encode(node).finish())
348
497
  await sendRawMessage(
349
- proto.HandshakeMessage.encode({
350
- clientFinish: { static: keyEnc, payload: payloadEnc }
351
- }).finish()
498
+ proto.HandshakeMessage.encode({ clientFinish: { static: keyEnc, payload: payloadEnc } }).finish()
352
499
  )
353
500
  await noise.finishInit()
354
501
  startKeepAliveRequest()
@@ -376,27 +523,32 @@ export const makeSocket = (config) => {
376
523
  * Keep-alive: ping WA every keepAliveIntervalMs.
377
524
  * If the server stops responding (diff > interval + 5s) the connection
378
525
  * is considered lost and we call end() — the consumer handles reconnection.
379
- * No internal reconnect loop — clean separation of concerns.
380
526
  */
381
527
  const startKeepAliveRequest = () => {
382
528
  keepAliveReq = setInterval(() => {
383
529
  if (!lastDateRecv) lastDateRecv = new Date()
384
530
  const diff = Date.now() - lastDateRecv.getTime()
385
531
  if (diff > keepAliveIntervalMs + 5000) {
386
- end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
532
+ void end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
387
533
  } else if (ws.isOpen) {
388
534
  query({
389
535
  tag: 'iq',
390
536
  attrs: { id: generateMessageTag(), to: S_WHATSAPP_NET, type: 'get', xmlns: 'w:p' },
391
537
  content: [{ tag: 'ping', attrs: {} }]
392
- }).catch((err) => logger.error({ trace: err.stack }, 'error in sending keep alive'))
538
+ }).catch((err) => logger.error({ trace: err.stack }, 'Error in sending keep alive'))
393
539
  } else {
394
- logger.warn('keep alive called when WS not open')
540
+ logger.warn('Keep alive called when WS not open')
395
541
  }
396
542
  }, keepAliveIntervalMs)
397
543
  }
398
544
 
399
- const end = (error) => {
545
+ /**
546
+ * Tear down the socket.
547
+ * Awaits ws.close() so cleanup is deterministic before emitting connection.update.
548
+ * Runs all registered socketEndHandlers in order.
549
+ * Calls signalRepository.close() and ev.destroy() to prevent leaks.
550
+ */
551
+ const end = async (error) => {
400
552
  if (closed) { logger.trace({ trace: error?.stack }, 'Connection already closed'); return }
401
553
  closed = true
402
554
  logger.info({ trace: error?.stack }, error ? 'connection errored' : 'connection closed')
@@ -405,9 +557,17 @@ export const makeSocket = (config) => {
405
557
  ws.removeAllListeners('close')
406
558
  ws.removeAllListeners('open')
407
559
  ws.removeAllListeners('message')
408
- if (!ws.isClosed && !ws.isClosing) { try { ws.close() } catch { } }
560
+ signalRepository.close?.()
561
+ if (!ws.isClosed && !ws.isClosing) {
562
+ try { await ws.close() } catch { }
563
+ }
564
+ for (const handler of socketEndHandlers) {
565
+ try { await handler(error) }
566
+ catch (err) { logger.error({ err }, 'error in socket end handler') }
567
+ }
409
568
  ev.emit('connection.update', { connection: 'close', lastDisconnect: { error, date: new Date() } })
410
569
  ev.removeAllListeners('connection.update')
570
+ ev.destroy()
411
571
  }
412
572
 
413
573
  const sendPassiveIq = (tag) =>
@@ -426,14 +586,21 @@ export const makeSocket = (config) => {
426
586
  content: [{ tag: 'remove-companion-device', attrs: { jid, reason: 'user_initiated' } }]
427
587
  })
428
588
  }
429
- end(new Boom(msg || 'Intentional Logout', { statusCode: DisconnectReason.loggedOut }))
589
+ void end(new Boom(msg || 'Intentional Logout', { statusCode: DisconnectReason.loggedOut }))
430
590
  }
431
591
 
592
+ /**
593
+ * Register a cleanup handler that will be awaited during end().
594
+ * Use for releasing external resources tied to this socket's lifetime.
595
+ */
596
+ const registerSocketEndHandler = (handler) => socketEndHandlers.push(handler)
597
+
432
598
  // ─── Pairing ─────────────────────────────────────────────────────────────────
433
599
 
434
600
  const requestPairingCode = async (phoneNumber, customPairingCode) => {
435
601
  await waitForSocketOpen()
436
- await new Promise(resolve => setTimeout(resolve, 500))
602
+ // Brief stabilisation delay ensures the WS open event has fully propagated
603
+ await new Promise((resolve) => setTimeout(resolve, 500))
437
604
  const pairingCode = customPairingCode ?? bytesToCrockford(randomBytes(5))
438
605
  if (customPairingCode && customPairingCode?.length !== 8)
439
606
  throw new Error('Custom pairing code must be exactly 8 chars')
@@ -449,7 +616,7 @@ export const makeSocket = (config) => {
449
616
  content: [
450
617
  { tag: 'link_code_pairing_wrapped_companion_ephemeral_pub', attrs: {}, content: await generatePairingKey() },
451
618
  { tag: 'companion_server_auth_key_pub', attrs: {}, content: authState.creds.noiseKey.public },
452
- { tag: 'companion_platform_id', attrs: {}, content: getPlatformId(browser[1]) },
619
+ { tag: 'companion_platform_id', attrs: {}, content: getCompanionPlatformId(browser) },
453
620
  { tag: 'companion_platform_display', attrs: {}, content: `${browser[1]} (${browser[0]})` },
454
621
  { tag: 'link_code_pairing_nonce', attrs: {}, content: '0' }
455
622
  ]
@@ -492,31 +659,22 @@ export const makeSocket = (config) => {
492
659
  })
493
660
  }
494
661
 
495
- const sendWAMBuffer = (wamBuffer) =>
496
- query({
497
- tag: 'iq',
498
- attrs: { to: S_WHATSAPP_NET, id: generateMessageTag(), xmlns: 'w:stats' },
499
- content: [{ tag: 'add', attrs: { t: Math.round(Date.now() / 1000) + '' }, content: wamBuffer }]
500
- })
501
-
502
662
  // ─── WebSocket event bindings ────────────────────────────────────────────────
503
663
 
504
664
  ws.on('message', onMessageReceived)
505
665
 
506
666
  ws.on('open', async () => {
507
667
  try { await validateConnection() }
508
- catch (err) { logger.error({ err }, 'error in validating connection'); end(err) }
668
+ catch (err) { logger.error({ err }, 'error in validating connection'); void end(err) }
509
669
  })
510
670
 
511
- // Let mapWebSocketError convert the raw error then call end()
512
671
  ws.on('error', mapWebSocketError(end))
513
672
 
514
- // Any close end(), consumer decides whether to reconnect
515
- ws.on('close', () => end(new Boom('Connection Terminated', { statusCode: DisconnectReason.connectionClosed })))
673
+ ws.on('close', () => void end(new Boom('Connection Terminated', { statusCode: DisconnectReason.connectionClosed })))
516
674
 
517
675
  ws.on('CB:xmlstreamend', () => {
518
676
  logger.info('Stream ended by server')
519
- if (!closed) end(new Boom('Connection Terminated by Server', { statusCode: DisconnectReason.connectionClosed }))
677
+ if (!closed) void end(new Boom('Connection Terminated by Server', { statusCode: DisconnectReason.connectionClosed }))
520
678
  })
521
679
 
522
680
  // ─── QR pairing ─────────────────────────────────────────────────────────────
@@ -532,9 +690,10 @@ export const makeSocket = (config) => {
532
690
  const genPairQR = () => {
533
691
  if (!ws.isOpen) return
534
692
  const refNode = refNodes.shift()
535
- if (!refNode) { end(new Boom('QR refs attempts ended', { statusCode: DisconnectReason.timedOut })); return }
693
+ if (!refNode) { void end(new Boom('QR refs attempts ended', { statusCode: DisconnectReason.timedOut })); return }
536
694
  const ref = refNode.content.toString('utf-8')
537
- const qr = [ref, noiseKeyB64, identityKeyB64, advB64].join(',')
695
+ // Use buildPairingQRData so the browser tuple is included in the QR payload
696
+ const qr = buildPairingQRData(ref, noiseKeyB64, identityKeyB64, advB64, browser)
538
697
  ev.emit('connection.update', { qr })
539
698
  qrTimer = setTimeout(genPairQR, qrMs)
540
699
  qrMs = qrTimeout || 20000
@@ -554,7 +713,7 @@ export const makeSocket = (config) => {
554
713
  void sendUnifiedSession()
555
714
  } catch (error) {
556
715
  logger.info({ trace: error.stack }, 'Error in pairing')
557
- end(error)
716
+ void end(error)
558
717
  }
559
718
  })
560
719
 
@@ -565,15 +724,11 @@ export const makeSocket = (config) => {
565
724
  updateServerTimeOffset(node)
566
725
  await uploadPreKeysToServerIfRequired()
567
726
  await sendPassiveIq('active')
568
- try {
569
- await digestKeyBundle()
570
- } catch (e) {
571
- logger.warn({ e }, 'failed to run digest after login')
572
- }
727
+ try { await digestKeyBundle() }
728
+ catch (e) { logger.warn({ e }, 'failed to run digest after login') }
573
729
  } catch (err) {
574
730
  logger.warn({ err }, 'Failed to send initial passive IQ')
575
731
  }
576
-
577
732
  logger.info('✅ Opened connection to WhatsApp')
578
733
  clearTimeout(qrTimer)
579
734
  ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } })
@@ -585,18 +740,22 @@ export const makeSocket = (config) => {
585
740
  process.nextTick(async () => {
586
741
  try {
587
742
  const myPN = authState.creds.me.id
743
+ // Store own LID-PN mapping
588
744
  await signalRepository.lidMapping.storeLIDPNMappings([{ lid: myLID, pn: myPN }])
745
+ // Build device-list using index-based batching to avoid unbounded key growth
589
746
  const { user, device } = jidDecode(myPN)
590
747
  const currentBatch = await migrateIndexKey(authState.keys, 'device-list')
591
748
  currentBatch[user] = [device?.toString() || '0']
592
749
  const deviceKeys = Object.keys(currentBatch)
593
750
  if (deviceKeys.length > BATCH_SIZE) {
594
751
  deviceKeys.sort()
595
- deviceKeys.slice(0, deviceKeys.length - BATCH_SIZE).forEach(k => delete currentBatch[k])
752
+ deviceKeys.slice(0, deviceKeys.length - BATCH_SIZE).forEach((k) => delete currentBatch[k])
596
753
  }
597
- await authState.keys.set({ 'device-list': { 'index': currentBatch } })
754
+ await authState.keys.set({ 'device-list': { index: currentBatch } })
755
+ // Migrate own session from PN → LID
598
756
  await signalRepository.migrateSession(myPN, myLID)
599
757
  logger.info({ myPN, myLID }, 'Own LID session created successfully')
758
+ // Batch-migrate any remaining PN sessions to LID
600
759
  if (signalRepository.migrateAllPNSessionsToLID) {
601
760
  try {
602
761
  const migrated = await signalRepository.migrateAllPNSessionsToLID()
@@ -615,37 +774,37 @@ export const makeSocket = (config) => {
615
774
  // ─── Stream / connection error handlers ─────────────────────────────────────
616
775
 
617
776
  ws.on('CB:stream:error', (node) => {
618
- logger.error({ node }, 'Stream errored out')
777
+ const [reasonNode] = getAllBinaryNodeChildren(node)
778
+ logger.error({ reasonNode, fullErrorNode: node }, 'Stream errored out')
619
779
  const { reason, statusCode } = getErrorCodeFromStreamError(node)
620
- end(new Boom(`Stream Errored (${reason})`, { statusCode, data: node }))
780
+ void end(new Boom(`Stream Errored (${reason})`, { statusCode, data: reasonNode || node }))
621
781
  })
622
782
 
623
783
  ws.on('CB:failure', (node) => {
624
784
  const reason = +(node.attrs.reason || 500)
625
- end(new Boom('Connection Failure', { statusCode: reason, data: node.attrs }))
785
+ void end(new Boom('Connection Failure', { statusCode: reason, data: node.attrs }))
626
786
  })
627
787
 
628
788
  ws.on('CB:ib,,downgrade_webclient', () =>
629
- end(new Boom('Multi-device beta not joined', { statusCode: DisconnectReason.multideviceMismatch }))
789
+ void end(new Boom('Multi-device beta not joined', { statusCode: DisconnectReason.multideviceMismatch }))
630
790
  )
631
791
 
632
- ws.on('CB:ib,,offline_preview', (node) => {
792
+ ws.on('CB:ib,,offline_preview', async (node) => {
633
793
  logger.info('Offline preview received', JSON.stringify(node))
634
- sendNode({ tag: 'ib', attrs: {}, content: [{ tag: 'offline_batch', attrs: { count: '100' } }] })
794
+ await sendNode({ tag: 'ib', attrs: {}, content: [{ tag: 'offline_batch', attrs: { count: '100' } }] })
635
795
  })
636
796
 
637
797
  ws.on('CB:ib,,edge_routing', (node) => {
638
798
  const edgeRoutingNode = getBinaryNodeChild(node, 'edge_routing')
639
799
  const routingInfo = getBinaryNodeChild(edgeRoutingNode, 'routing_info')
640
800
  if (routingInfo?.content) {
641
- authState.creds.routingInfo = Buffer.from(routingInfo?.content)
801
+ authState.creds.routingInfo = Buffer.from(routingInfo.content)
642
802
  ev.emit('creds.update', authState.creds)
643
803
  }
644
804
  })
645
805
 
646
806
  // ─── Buffering & offline notifications ──────────────────────────────────────
647
807
 
648
- let didStartBuffer = false
649
808
  process.nextTick(() => {
650
809
  if (creds.me?.id) { ev.buffer(); didStartBuffer = true }
651
810
  ev.emit('connection.update', { connection: 'connecting', receivedPendingNotifications: false, qr: undefined })
@@ -689,6 +848,7 @@ export const makeSocket = (config) => {
689
848
  sendNode,
690
849
  logout,
691
850
  end,
851
+ registerSocketEndHandler,
692
852
  onUnexpectedError,
693
853
  uploadPreKeys,
694
854
  uploadPreKeysToServerIfRequired,
@@ -701,6 +861,9 @@ export const makeSocket = (config) => {
701
861
  waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),
702
862
  sendWAMBuffer,
703
863
  executeUSyncQuery,
864
+ onWhatsApp,
865
+ fetchAccountReachoutTimelock,
866
+ fetchNewChatMessageCap,
704
867
  listener: (eventName) => {
705
868
  if (typeof ev.listenerCount === 'function') return ev.listenerCount(eventName)
706
869
  if (typeof ev.listener === 'function') return ev.listener(eventName)?.length || 0
@@ -1,29 +1,37 @@
1
- export const XWAPaths = {
2
- xwa2_newsletter_create: 'xwa2_newsletter_create',
3
- xwa2_newsletter_subscribers: 'xwa2_newsletter_subscribers',
4
- xwa2_newsletter_view: 'xwa2_newsletter_view',
5
- xwa2_newsletter_metadata: 'xwa2_newsletter',
6
- xwa2_newsletter_admin_count: 'xwa2_newsletter_admin',
7
- xwa2_newsletter_mute_v2: 'xwa2_newsletter_mute_v2',
8
- xwa2_newsletter_unmute_v2: 'xwa2_newsletter_unmute_v2',
9
- xwa2_newsletter_follow: 'xwa2_newsletter_follow',
10
- xwa2_newsletter_unfollow: 'xwa2_newsletter_unfollow',
11
- xwa2_newsletter_change_owner: 'xwa2_newsletter_change_owner',
12
- xwa2_newsletter_demote: 'xwa2_newsletter_demote',
13
- xwa2_newsletter_delete_v2: 'xwa2_newsletter_delete_v2'
14
- }
15
-
16
- export const QueryIds = {
17
- CREATE: '8823471724422422',
18
- UPDATE_METADATA: '24250201037901610',
19
- METADATA: '6563316087068696',
20
- SUBSCRIBERS: '9783111038412085',
21
- FOLLOW: '7871414976211147',
22
- UNFOLLOW: '7238632346214362',
23
- MUTE: '29766401636284406',
24
- UNMUTE: '9864994326891137',
25
- ADMIN_COUNT: '7130823597031706',
26
- CHANGE_OWNER: '7341777602580933',
27
- DEMOTE: '6551828931592903',
28
- DELETE: '30062808666639665'
29
- }
1
+ export var XWAPaths;
2
+ (function (XWAPaths) {
3
+ XWAPaths["xwa2_newsletter_create"] = "xwa2_newsletter_create";
4
+ XWAPaths["xwa2_newsletter_subscribers"] = "xwa2_newsletter_subscribers";
5
+ XWAPaths["xwa2_newsletter_view"] = "xwa2_newsletter_view";
6
+ XWAPaths["xwa2_newsletter_metadata"] = "xwa2_newsletter";
7
+ XWAPaths["xwa2_newsletter_admin_count"] = "xwa2_newsletter_admin";
8
+ XWAPaths["xwa2_newsletter_mute_v2"] = "xwa2_newsletter_mute_v2";
9
+ XWAPaths["xwa2_newsletter_unmute_v2"] = "xwa2_newsletter_unmute_v2";
10
+ XWAPaths["xwa2_newsletter_follow"] = "xwa2_newsletter_follow";
11
+ XWAPaths["xwa2_newsletter_unfollow"] = "xwa2_newsletter_unfollow";
12
+ XWAPaths["xwa2_newsletter_join_v2"] = "xwa2_newsletter_join_v2";
13
+ XWAPaths["xwa2_newsletter_leave_v2"] = "xwa2_newsletter_leave_v2";
14
+ XWAPaths["xwa2_newsletter_change_owner"] = "xwa2_newsletter_change_owner";
15
+ XWAPaths["xwa2_newsletter_demote"] = "xwa2_newsletter_demote";
16
+ XWAPaths["xwa2_newsletter_delete_v2"] = "xwa2_newsletter_delete_v2";
17
+ XWAPaths["xwa2_fetch_account_reachout_timelock"] = "xwa2_fetch_account_reachout_timelock";
18
+ XWAPaths["xwa2_message_capping_info"] = "xwa2_message_capping_info";
19
+ })(XWAPaths || (XWAPaths = {}));
20
+ export var QueryIds;
21
+ (function (QueryIds) {
22
+ QueryIds["CREATE"] = "8823471724422422";
23
+ QueryIds["UPDATE_METADATA"] = "24250201037901610";
24
+ QueryIds["METADATA"] = "6563316087068696";
25
+ QueryIds["SUBSCRIBERS"] = "9783111038412085";
26
+ QueryIds["FOLLOW"] = "24404358912487870";
27
+ QueryIds["UNFOLLOW"] = "9767147403369991";
28
+ QueryIds["MUTE"] = "29766401636284406";
29
+ QueryIds["UNMUTE"] = "9864994326891137";
30
+ QueryIds["ADMIN_COUNT"] = "7130823597031706";
31
+ QueryIds["CHANGE_OWNER"] = "7341777602580933";
32
+ QueryIds["DEMOTE"] = "6551828931592903";
33
+ QueryIds["DELETE"] = "30062808666639665";
34
+ QueryIds["REACHOUT_TIMELOCK"] = "23983697327930364";
35
+ QueryIds["MESSAGE_CAPPING_INFO"] = "24503548349331633";
36
+ })(QueryIds || (QueryIds = {}));
37
+ //# sourceMappingURL=Mex.js.map