@hansaka02/baileys 7.3.4 → 7.3.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.
Files changed (50) hide show
  1. package/README.md +203 -247
  2. package/lib/Defaults/baileys-version.json +2 -2
  3. package/lib/Defaults/connection.js +1 -1
  4. package/lib/Defaults/constants.js +13 -1
  5. package/lib/Defaults/history.js +3 -1
  6. package/lib/Signal/Group/sender-chain-key.js +1 -14
  7. package/lib/Signal/Group/sender-key-distribution-message.js +2 -2
  8. package/lib/Signal/Group/sender-key-record.js +2 -11
  9. package/lib/Signal/Group/sender-key-state.js +11 -57
  10. package/lib/Signal/libsignal.js +200 -116
  11. package/lib/Signal/lid-mapping.js +121 -68
  12. package/lib/Socket/Client/websocket.js +9 -2
  13. package/lib/Socket/business.js +5 -1
  14. package/lib/Socket/chats.js +180 -89
  15. package/lib/Socket/community.js +169 -41
  16. package/lib/Socket/groups.js +25 -21
  17. package/lib/Socket/messages-recv.js +458 -333
  18. package/lib/Socket/messages-send.js +517 -572
  19. package/lib/Socket/mex.js +61 -0
  20. package/lib/Socket/newsletter.js +159 -252
  21. package/lib/Socket/socket.js +283 -100
  22. package/lib/Types/Newsletter.js +32 -25
  23. package/lib/Utils/auth-utils.js +189 -354
  24. package/lib/Utils/browser-utils.js +43 -0
  25. package/lib/Utils/chat-utils.js +166 -41
  26. package/lib/Utils/decode-wa-message.js +77 -35
  27. package/lib/Utils/event-buffer.js +80 -24
  28. package/lib/Utils/generics.js +28 -128
  29. package/lib/Utils/history.js +10 -8
  30. package/lib/Utils/index.js +1 -1
  31. package/lib/Utils/link-preview.js +17 -32
  32. package/lib/Utils/lt-hash.js +28 -22
  33. package/lib/Utils/make-mutex.js +26 -28
  34. package/lib/Utils/message-retry-manager.js +51 -3
  35. package/lib/Utils/messages-media.js +343 -151
  36. package/lib/Utils/messages.js +806 -792
  37. package/lib/Utils/noise-handler.js +33 -2
  38. package/lib/Utils/pre-key-manager.js +126 -0
  39. package/lib/Utils/process-message.js +115 -55
  40. package/lib/Utils/signal.js +45 -18
  41. package/lib/Utils/validate-connection.js +52 -29
  42. package/lib/WABinary/constants.js +1268 -1268
  43. package/lib/WABinary/decode.js +58 -4
  44. package/lib/WABinary/encode.js +54 -7
  45. package/lib/WABinary/jid-utils.js +58 -11
  46. package/lib/WAM/constants.js +19064 -11563
  47. package/lib/WAM/encode.js +57 -8
  48. package/lib/WAUSync/USyncQuery.js +35 -19
  49. package/package.json +9 -8
  50. package/lib/Socket/usync.js +0 -83
@@ -46,11 +46,13 @@ const {
46
46
  encodeBinaryNode,
47
47
  getBinaryNodeChild,
48
48
  getBinaryNodeChildren,
49
+ getAllBinaryNodeChildren,
49
50
  isLidUser,
50
51
  jidDecode,
51
52
  jidEncode,
52
- S_WHATSAPP_NET
53
+ S_WHATSAPP_NET
53
54
  } = require("../WABinary")
55
+ const { BinaryInfo } = require("../WAM")
54
56
  const {
55
57
  USyncUser,
56
58
  USyncQuery
@@ -64,11 +66,23 @@ const { WebSocketClient } = require("./Client")
64
66
  * - query phone connection
65
67
  */
66
68
  const makeSocket = (config) => {
67
- const { waWebSocketUrl, connectTimeoutMs, logger, keepAliveIntervalMs, browser, auth: authState, printQRInTerminal, defaultQueryTimeoutMs, transactionOpts, qrTimeout, makeSignalRepository } = config
69
+ const {
70
+ waWebSocketUrl,
71
+ connectTimeoutMs,
72
+ logger,
73
+ keepAliveIntervalMs,
74
+ browser,
75
+ auth: authState,
76
+ printQRInTerminal,
77
+ defaultQueryTimeoutMs,
78
+ transactionOpts,
79
+ qrTimeout,
80
+ makeSignalRepository
81
+ } = config
68
82
 
69
83
  const uqTagId = generateMdTagPrefix()
70
84
  const generateMessageTag = () => `${uqTagId}${epoch++}`
71
-
85
+ const publicWAMBuffer = new BinaryInfo()
72
86
  const url = typeof waWebSocketUrl === 'string' ? new URL(waWebSocketUrl) : waWebSocketUrl
73
87
 
74
88
  if (config.mobile || url.protocol === 'tcp:') {
@@ -79,6 +93,53 @@ const makeSocket = (config) => {
79
93
  url.searchParams.append('ED', authState.creds.routingInfo.toString('base64url'))
80
94
  }
81
95
 
96
+ /** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
97
+ const ephemeralKeyPair = Curve.generateKeyPair()
98
+
99
+ /** WA noise protocol wrapper */
100
+ const noise = makeNoiseHandler({
101
+ keyPair: ephemeralKeyPair,
102
+ NOISE_HEADER: NOISE_WA_HEADER,
103
+ logger,
104
+ routingInfo: authState?.creds?.routingInfo
105
+ })
106
+
107
+ const ws = new WebSocketClient(url, config)
108
+
109
+ ws.connect()
110
+
111
+ const sendPromise = promisify(ws.send)
112
+
113
+ /** send a raw buffer */
114
+ const sendRawMessage = async (data) => {
115
+ if (!ws.isOpen) {
116
+ throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
117
+ }
118
+
119
+ const bytes = noise.encodeFrame(data)
120
+
121
+ await promiseTimeout(connectTimeoutMs, async (resolve, reject) => {
122
+ try {
123
+ await sendPromise.call(ws, bytes)
124
+ resolve()
125
+ }
126
+ catch (error) {
127
+ reject(error)
128
+ }
129
+ })
130
+ }
131
+
132
+ /** send a binary node */
133
+ const sendNode = (frame) => {
134
+ if (logger.level === 'trace') {
135
+ logger.trace({ xml: binaryNodeToString(frame), msg: 'xml send' })
136
+ }
137
+
138
+ const buff = encodeBinaryNode(frame)
139
+
140
+ return sendRawMessage(buff)
141
+ }
142
+
82
143
  /**
83
144
  * Wait for a message with a certain tag to be received
84
145
  * @param msgId the message tag to await
@@ -87,22 +148,47 @@ const makeSocket = (config) => {
87
148
  const waitForMessage = async (msgId, timeoutMs = defaultQueryTimeoutMs) => {
88
149
  let onRecv
89
150
  let onErr
151
+
90
152
  try {
91
- return await promiseTimeout(timeoutMs, (resolve, reject) => {
92
- onRecv = resolve
153
+ const result = await promiseTimeout(timeoutMs, (resolve, reject) => {
154
+ onRecv = data => {
155
+ resolve(data)
156
+ }
157
+
93
158
  onErr = err => {
94
- reject(err || new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed }))
159
+ reject(err ||
160
+ new Boom('Connection Closed', {
161
+ statusCode: DisconnectReason.connectionClosed
162
+ }))
95
163
  }
164
+
96
165
  ws.on(`TAG:${msgId}`, onRecv)
97
- ws.on('close', onErr) // if the socket closes, you'll never receive the message
98
- ws.off('error', onErr)
166
+ ws.on('close', onErr)
167
+ ws.on('error', onErr)
168
+
169
+ return () => reject(new Boom('Query Cancelled'))
99
170
  })
171
+
172
+ return result
173
+ }
174
+
175
+ catch (error) {
176
+ // Catch timeout and return undefined instead of throwing
177
+ if (error instanceof Boom && error.output?.statusCode === DisconnectReason.timedOut) {
178
+ logger?.warn?.({ msgId }, 'timed out waiting for message')
179
+ return undefined
180
+ }
181
+
182
+ throw error
100
183
  }
101
184
 
102
185
  finally {
103
- ws.off(`TAG:${msgId}`, onRecv)
104
- ws.off('close', onErr) // if the socket closes, you'll never receive the message
105
- ws.off('error', onErr)
186
+ if (onRecv)
187
+ ws.off(`TAG:${msgId}`, onRecv)
188
+ if (onErr) {
189
+ ws.off('close', onErr)
190
+ ws.off('error', onErr)
191
+ }
106
192
  }
107
193
  }
108
194
 
@@ -113,22 +199,60 @@ const makeSocket = (config) => {
113
199
  }
114
200
 
115
201
  const msgId = node.attrs.id
116
- const wait = waitForMessage(msgId, timeoutMs)
117
-
118
- await sendNode(node)
119
-
120
- const result = await wait
202
+ const result = await promiseTimeout(timeoutMs, async (resolve, reject) => {
203
+ const result = waitForMessage(msgId, timeoutMs).catch(reject)
204
+ sendNode(node)
205
+ .then(async () => resolve(await result))
206
+ .catch(reject)
207
+ })
121
208
 
122
- if ('tag' in result) {
209
+ if (result && 'tag' in result) {
123
210
  assertNodeErrorFree(result)
124
211
  }
125
212
 
126
213
  return result
127
214
  }
128
215
 
216
+ // Validate current key-bundle on server; on failure, trigger pre-key upload and rethrow
217
+ const digestKeyBundle = async () => {
218
+ const res = await query({
219
+ tag: 'iq',
220
+ attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'encrypt' },
221
+ content: [{ tag: 'digest', attrs: {} }]
222
+ })
223
+
224
+ const digestNode = getBinaryNodeChild(res, 'digest')
225
+
226
+ if (!digestNode) {
227
+ await uploadPreKeys()
228
+ throw new Error('encrypt/get digest returned no digest node')
229
+ }
230
+ }
231
+
232
+ // Rotate our signed pre-key on server; on failure, run digest as fallback and rethrow
233
+ const rotateSignedPreKey = async () => {
234
+ const newId = (creds.signedPreKey.keyId || 0) + 1
235
+ const skey = await signedKeyPair(creds.signedIdentityKey, newId)
236
+
237
+ await query({
238
+ tag: 'iq',
239
+ attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'encrypt' },
240
+ content: [
241
+ {
242
+ tag: 'rotate',
243
+ attrs: {},
244
+ content: [xmppSignedPreKey(skey)]
245
+ }
246
+ ]
247
+ })
248
+
249
+ // Persist new signed pre-key in creds
250
+ ev.emit('creds.update', { signedPreKey: skey })
251
+ }
252
+
129
253
  const executeUSyncQuery = async (usyncQuery) => {
130
254
  if (usyncQuery.protocols.length === 0) {
131
- throw new Boom('USyncQuery must have at least one protocol');
255
+ throw new Boom('USyncQuery must have at least one protocol')
132
256
  }
133
257
 
134
258
  // todo: validate users, throw WARNING on no valid users
@@ -179,53 +303,74 @@ const makeSocket = (config) => {
179
303
  }
180
304
 
181
305
  const result = await query(iq)
306
+
182
307
  return usyncQuery.parseUSyncQueryResult(result)
183
308
  }
184
309
 
185
- const onWhatsApp = async (...jids) => {
186
- const usyncQuery = new USyncQuery().withLIDProtocol().withContactProtocol()
187
- for (const jid of jids) {
310
+ const onWhatsApp = async (...phoneNumber) => {
311
+ let usyncQuery = new USyncQuery()
312
+ let contactEnabled = false
313
+
314
+ for (const jid of phoneNumber) {
188
315
  if (isLidUser(jid)) {
189
- usyncQuery.withUser(new USyncUser().withId(jid)) // intentional
316
+ logger?.warn('LIDs are not supported with onWhatsApp')
317
+ continue
190
318
  }
319
+
191
320
  else {
321
+ if (!contactEnabled) {
322
+ contactEnabled = true
323
+ usyncQuery = usyncQuery.withContactProtocol()
324
+ }
325
+
192
326
  const phone = `+${jid.replace('+', '').split('@')[0]?.split(':')[0]}`
193
327
  usyncQuery.withUser(new USyncUser().withPhone(phone))
194
328
  }
195
329
  }
330
+
331
+ if (usyncQuery.users.length === 0) {
332
+ return [] // return early without forcing an empty query
333
+ }
334
+
196
335
  const results = await executeUSyncQuery(usyncQuery)
336
+
197
337
  if (results) {
198
- if (results.list.filter(a => !!a.lid).length > 0) {
199
- const lidOnly = results.list.filter(a => !!a.lid)
200
- await signalRepository.lidMapping.storeLIDPNMappings(lidOnly.map(a => ({ pn: a.id, lid: a.lid })))
201
- }
202
- return results.list
203
- .filter(a => !!a.contact)
204
- .map(({ contact, id, lid }) => ({ jid: id, exists: contact, lid: lid }))
338
+ return results.list.filter(a => !!a.contact).map(({ contact, id }) => ({ jid: id, exists: contact }))
205
339
  }
206
340
  }
207
341
 
208
- const ws = new WebSocketClient(url, config)
342
+ const pnFromLIDUSync = async (jids) => {
343
+ const usyncQuery = new USyncQuery().withLIDProtocol().withContext('background')
344
+ for (const jid of jids) {
345
+ if (isLidUser(jid)) {
346
+ logger?.warn('LID user found in LID fetch call')
347
+ continue
348
+ }
349
+
350
+ else {
351
+ usyncQuery.withUser(new USyncUser().withId(jid))
352
+ }
353
+ }
354
+
355
+ if (usyncQuery.users.length === 0) {
356
+ return [] // return early without forcing an empty query
357
+ }
358
+
359
+ const results = await executeUSyncQuery(usyncQuery)
360
+
361
+ if (results) {
362
+ return results.list.filter(a => !!a.lid).map(({ lid, id }) => ({ pn: id, lid: lid }))
363
+ }
364
+
365
+ return []
366
+ }
209
367
 
210
- ws.connect()
211
368
  const ev = makeEventBuffer(logger)
212
-
213
- /** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
214
- const ephemeralKeyPair = Curve.generateKeyPair()
215
-
216
- /** WA noise protocol wrapper */
217
- const noise = makeNoiseHandler({
218
- keyPair: ephemeralKeyPair,
219
- NOISE_HEADER: NOISE_WA_HEADER,
220
- logger,
221
- routingInfo: authState?.creds?.routingInfo
222
- })
223
-
224
369
  const { creds } = authState
225
370
 
226
371
  // add transaction capability
227
372
  const keys = addTransactionCapability(authState.keys, logger, transactionOpts)
228
- const signalRepository = makeSignalRepository({ creds, keys }, onWhatsApp, logger)
373
+ const signalRepository = makeSignalRepository({ creds, keys }, logger, pnFromLIDUSync)
229
374
 
230
375
  let lastDateRecv
231
376
  let epoch = 1
@@ -233,37 +378,6 @@ const makeSocket = (config) => {
233
378
  let qrTimer
234
379
  let closed = false
235
380
 
236
- const sendPromise = promisify(ws.send)
237
-
238
- /** send a raw buffer */
239
- const sendRawMessage = async (data) => {
240
- if (!ws.isOpen) {
241
- throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
242
- }
243
-
244
- const bytes = noise.encodeFrame(data)
245
- await promiseTimeout(connectTimeoutMs, async (resolve, reject) => {
246
- try {
247
- await sendPromise.call(ws, bytes)
248
- resolve()
249
- }
250
- catch (error) {
251
- reject(error)
252
- }
253
- })
254
- }
255
-
256
- /** send a binary node */
257
- const sendNode = (frame) => {
258
- if (logger.level === 'trace') {
259
- logger.trace({ xml: binaryNodeToString(frame), msg: 'xml send' })
260
- }
261
-
262
- const buff = encodeBinaryNode(frame)
263
-
264
- return sendRawMessage(buff)
265
- }
266
-
267
381
  /** log & process any unexpected errors */
268
382
  const onUnexpectedError = (err, msg) => {
269
383
  logger.error({ err }, `unexpected error in '${msg}'`)
@@ -283,6 +397,7 @@ const makeSocket = (config) => {
283
397
  const result = promiseTimeout(connectTimeoutMs, (resolve, reject) => {
284
398
  onOpen = resolve
285
399
  onClose = mapWebSocketError(reject)
400
+
286
401
  ws.on('frame', onOpen)
287
402
  ws.on('close', onClose)
288
403
  ws.on('error', onClose)
@@ -306,6 +421,7 @@ const makeSocket = (config) => {
306
421
  }
307
422
 
308
423
  helloMsg = proto.HandshakeMessage.fromObject(helloMsg)
424
+
309
425
  logger.info({ browser, helloMsg }, 'connected to WA')
310
426
 
311
427
  const init = proto.HandshakeMessage.encode(helloMsg).finish()
@@ -315,6 +431,7 @@ const makeSocket = (config) => {
315
431
  logger.trace({ handshake }, 'handshake recv from WA')
316
432
 
317
433
  const keyEnc = await noise.processHandshake(handshake, creds.noiseKey)
434
+
318
435
  let node
319
436
 
320
437
  if (!creds.me) {
@@ -326,15 +443,18 @@ const makeSocket = (config) => {
326
443
  node = generateLoginNode(creds.me.id, config)
327
444
  logger.info({ node }, 'logging in...')
328
445
  }
446
+
329
447
  const payloadEnc = noise.encrypt(proto.ClientPayload.encode(node).finish())
330
448
 
331
449
  await sendRawMessage(proto.HandshakeMessage.encode({
332
450
  clientFinish: {
333
451
  static: keyEnc,
334
- payload: payloadEnc,
335
- },
452
+ payload: payloadEnc
453
+ }
336
454
  }).finish())
337
- noise.finishInit()
455
+
456
+ await noise.finishInit()
457
+
338
458
  startKeepAliveRequest()
339
459
  }
340
460
 
@@ -362,10 +482,11 @@ const makeSocket = (config) => {
362
482
  let lastUploadTime = 0
363
483
 
364
484
  /** generates and uploads a set of pre-keys to the server */
365
- const uploadPreKeys = async (count = INITIAL_PREKEY_COUNT, retryCount = 0) => {
485
+ const uploadPreKeys = async (count = MIN_PREKEY_COUNT, retryCount = 0) => {
366
486
  // Check minimum interval (except for retries)
367
487
  if (retryCount === 0) {
368
488
  const timeSinceLastUpload = Date.now() - lastUploadTime
489
+
369
490
  if (timeSinceLastUpload < MIN_UPLOAD_INTERVAL) {
370
491
  logger.debug(`Skipping upload, only ${timeSinceLastUpload}ms since last upload`)
371
492
  return
@@ -375,7 +496,7 @@ const makeSocket = (config) => {
375
496
  // Prevent multiple concurrent uploads
376
497
  if (uploadPreKeysPromise) {
377
498
  logger.debug('Pre-key upload already in progress, waiting for completion')
378
- return uploadPreKeysPromise
499
+ await uploadPreKeysPromise
379
500
  }
380
501
 
381
502
  const uploadLogic = async () => {
@@ -384,6 +505,7 @@ const makeSocket = (config) => {
384
505
  // Generate and save pre-keys atomically (prevents ID collisions on retry)
385
506
  const node = await keys.transaction(async () => {
386
507
  logger.debug({ requestedCount: count }, 'generating pre-keys with requested count')
508
+
387
509
  const { update, node } = await getNextPreKeysNode({ creds, keys }, count)
388
510
 
389
511
  // Update credentials immediately to prevent duplicate IDs on retry
@@ -398,15 +520,21 @@ const makeSocket = (config) => {
398
520
  logger.info({ count }, 'uploaded pre-keys successfully')
399
521
  lastUploadTime = Date.now()
400
522
  }
523
+
401
524
  catch (uploadError) {
402
- logger.error({ uploadError, count }, 'Failed to upload pre-keys to server')
525
+ logger.error({ uploadError: uploadError.toString(), count }, 'Failed to upload pre-keys to server')
526
+
403
527
  // Exponential backoff retry (max 3 retries)
404
528
  if (retryCount < 3) {
405
529
  const backoffDelay = Math.min(1000 * Math.pow(2, retryCount), 10000)
530
+
406
531
  logger.info(`Retrying pre-key upload in ${backoffDelay}ms`)
532
+
407
533
  await new Promise(resolve => setTimeout(resolve, backoffDelay))
534
+
408
535
  return uploadPreKeys(count, retryCount + 1)
409
536
  }
537
+
410
538
  throw uploadError
411
539
  }
412
540
  }
@@ -416,9 +544,11 @@ const makeSocket = (config) => {
416
544
  uploadLogic(),
417
545
  new Promise((_, reject) => setTimeout(() => reject(new Boom('Pre-key upload timeout', { statusCode: 408 })), UPLOAD_TIMEOUT))
418
546
  ])
547
+
419
548
  try {
420
549
  await uploadPreKeysPromise
421
550
  }
551
+
422
552
  finally {
423
553
  uploadPreKeysPromise = null
424
554
  }
@@ -426,50 +556,69 @@ const makeSocket = (config) => {
426
556
 
427
557
  const verifyCurrentPreKeyExists = async () => {
428
558
  const currentPreKeyId = creds.nextPreKeyId - 1
559
+
429
560
  if (currentPreKeyId <= 0) {
430
561
  return { exists: false, currentPreKeyId: 0 }
431
562
  }
563
+
432
564
  const preKeys = await keys.get('pre-key', [currentPreKeyId.toString()])
433
565
  const exists = !!preKeys[currentPreKeyId.toString()]
566
+
434
567
  return { exists, currentPreKeyId }
435
568
  }
436
569
 
437
570
  const uploadPreKeysToServerIfRequired = async () => {
438
571
  try {
572
+ let count = 0
573
+
439
574
  const preKeyCount = await getAvailablePreKeysOnServer()
575
+
576
+ if (preKeyCount === 0)
577
+ count = INITIAL_PREKEY_COUNT
578
+ else
579
+ count = MIN_PREKEY_COUNT
580
+
440
581
  const { exists: currentPreKeyExists, currentPreKeyId } = await verifyCurrentPreKeyExists()
441
582
 
442
583
  logger.info(`${preKeyCount} pre-keys found on server`)
443
584
  logger.info(`Current prekey ID: ${currentPreKeyId}, exists in storage: ${currentPreKeyExists}`)
444
585
 
445
- const lowServerCount = preKeyCount <= MIN_PREKEY_COUNT
586
+ const lowServerCount = preKeyCount <= count
446
587
  const missingCurrentPreKey = !currentPreKeyExists && currentPreKeyId > 0
447
588
  const shouldUpload = lowServerCount || missingCurrentPreKey
448
589
 
449
590
  if (shouldUpload) {
450
591
  const reasons = []
592
+
451
593
  if (lowServerCount)
452
594
  reasons.push(`server count low (${preKeyCount})`)
595
+
453
596
  if (missingCurrentPreKey)
454
597
  reasons.push(`current prekey ${currentPreKeyId} missing from storage`)
598
+
455
599
  logger.info(`Uploading PreKeys due to: ${reasons.join(', ')}`)
456
- await uploadPreKeys()
600
+
601
+ await uploadPreKeys(count)
457
602
  }
603
+
458
604
  else {
459
605
  logger.info(`PreKey validation passed - Server: ${preKeyCount}, Current prekey ${currentPreKeyId} exists`)
460
606
  }
461
607
  }
608
+
462
609
  catch (error) {
463
610
  logger.error({ error }, 'Failed to check/upload pre-keys during initialization')
464
611
  // Don't throw - allow connection to continue even if pre-key check fails
465
612
  }
466
613
  }
467
614
 
468
- const onMessageReceived = (data) => {
469
- noise.decodeFrame(data, frame => {
615
+ const onMessageReceived = async (data) => {
616
+ await noise.decodeFrame(data, frame => {
470
617
  // reset ping timeout
471
618
  lastDateRecv = new Date()
619
+
472
620
  let anyTriggered = false
621
+
473
622
  anyTriggered = ws.emit('frame', frame)
474
623
 
475
624
  // if it's a binary node
@@ -513,8 +662,10 @@ const makeSocket = (config) => {
513
662
  closed = true
514
663
 
515
664
  logger.info({ trace: error?.stack }, error ? 'connection errored' : 'connection closed')
665
+
516
666
  clearInterval(keepAliveReq)
517
667
  clearTimeout(qrTimer)
668
+
518
669
  ws.removeAllListeners('close')
519
670
  ws.removeAllListeners('open')
520
671
  ws.removeAllListeners('message')
@@ -523,7 +674,7 @@ const makeSocket = (config) => {
523
674
  try {
524
675
  ws.close()
525
676
  }
526
- catch (_a) { }
677
+ catch { }
527
678
  }
528
679
 
529
680
  ev.emit('connection.update', {
@@ -543,7 +694,7 @@ const makeSocket = (config) => {
543
694
  }
544
695
 
545
696
  if (ws.isClosed || ws.isClosing) {
546
- throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
697
+ throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed });
547
698
  }
548
699
 
549
700
  let onOpen
@@ -552,6 +703,7 @@ const makeSocket = (config) => {
552
703
  await new Promise((resolve, reject) => {
553
704
  onOpen = () => resolve(undefined)
554
705
  onClose = mapWebSocketError(reject)
706
+
555
707
  ws.on('open', onOpen)
556
708
  ws.on('close', onClose)
557
709
  ws.on('error', onClose)
@@ -663,7 +815,6 @@ const makeSocket = (config) => {
663
815
  attrs: {
664
816
  jid: authState.creds.me.id,
665
817
  stage: 'companion_hello',
666
- // eslint-disable-next-line camelcase
667
818
  should_show_push_notification: 'true'
668
819
  },
669
820
  content: [
@@ -720,7 +871,7 @@ const makeSocket = (config) => {
720
871
  content: [
721
872
  {
722
873
  tag: 'add',
723
- attrs: {},
874
+ attrs: { t: Math.round(Date.now() / 1000) + '' },
724
875
  content: wamBuffer
725
876
  }
726
877
  ]
@@ -810,21 +961,37 @@ const makeSocket = (config) => {
810
961
  }
811
962
  })
812
963
 
964
+ // login complete
813
965
  // login complete
814
966
  ws.on('CB:success', async (node) => {
815
967
  try {
816
968
  await uploadPreKeysToServerIfRequired()
817
969
  await sendPassiveIq('active')
970
+
971
+ // After successful login, validate our key-bundle against server
972
+ try {
973
+ await digestKeyBundle()
974
+ }
975
+
976
+ catch (e) {
977
+ logger.warn({ e }, 'failed to run digest after login');
978
+ }
818
979
  }
980
+
819
981
  catch (err) {
820
- logger.warn({ err }, 'failed to send initial passive iq');
982
+ logger.warn({ err }, 'failed to send initial passive iq')
821
983
  }
984
+
822
985
  logger.info('opened connection to WA')
823
- clearTimeout(qrTimer); // will never happen in all likelyhood -- but just in case WA sends success on first try
986
+
987
+ clearTimeout(qrTimer) // will never happen in all likelyhood -- but just in case WA sends success on first try
988
+
824
989
  ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } })
825
990
  ev.emit('connection.update', { connection: 'open' })
991
+
826
992
  if (node.attrs.lid && authState.creds.me?.id) {
827
993
  const myLID = node.attrs.lid
994
+
828
995
  process.nextTick(async () => {
829
996
  try {
830
997
  const myPN = authState.creds.me.id
@@ -832,10 +999,21 @@ const makeSocket = (config) => {
832
999
  // Store our own LID-PN mapping
833
1000
  await signalRepository.lidMapping.storeLIDPNMappings([{ lid: myLID, pn: myPN }])
834
1001
 
835
- // Create LID session for ourselves (whatsmeow pattern)
836
- await signalRepository.migrateSession([myPN], myLID)
1002
+ // Create device list for our own user (needed for bulk migration)
1003
+ const { user, device } = jidDecode(myPN)
1004
+
1005
+ await authState.keys.set({
1006
+ 'device-list': {
1007
+ [user]: [device?.toString() || '0']
1008
+ }
1009
+ })
1010
+
1011
+ // migrate our own session
1012
+ await signalRepository.migrateSession(myPN, myLID)
1013
+
837
1014
  logger.info({ myPN, myLID }, 'Own LID session created successfully')
838
1015
  }
1016
+
839
1017
  catch (error) {
840
1018
  logger.error({ error, lid: myLID }, 'Failed to create own LID session')
841
1019
  }
@@ -844,11 +1022,14 @@ const makeSocket = (config) => {
844
1022
  })
845
1023
 
846
1024
  ws.on('CB:stream:error', (node) => {
847
- logger.error({ node }, 'stream errored out')
1025
+ const [reasonNode] = getAllBinaryNodeChildren(node)
1026
+
1027
+ logger.error({ reasonNode, fullErrorNode: node }, 'stream errored out')
1028
+
848
1029
  const { reason, statusCode } = getErrorCodeFromStreamError(node)
849
1030
 
850
- end(new Boom(`Stream Errored (${reason})`, { statusCode, data: node }))
851
- })
1031
+ end(new Boom(`Stream Errored (${reason})`, { statusCode, data: reasonNode || node }))
1032
+ });
852
1033
 
853
1034
  // stream fail, possible logout
854
1035
  ws.on('CB:failure', (node) => {
@@ -939,7 +1120,7 @@ const makeSocket = (config) => {
939
1120
  authState: { creds, keys },
940
1121
  signalRepository,
941
1122
  get user() {
942
- return authState.creds.me
1123
+ return authState.creds.me;
943
1124
  },
944
1125
  generateMessageTag,
945
1126
  query,
@@ -952,13 +1133,15 @@ const makeSocket = (config) => {
952
1133
  onUnexpectedError,
953
1134
  uploadPreKeys,
954
1135
  uploadPreKeysToServerIfRequired,
1136
+ digestKeyBundle,
1137
+ rotateSignedPreKey,
955
1138
  requestPairingCode,
1139
+ wamBuffer: publicWAMBuffer,
956
1140
  /** Waits for the connection to WA to reach a state */
957
1141
  waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),
958
1142
  sendWAMBuffer,
959
- executeUSyncQuery,
960
- onWhatsApp,
961
- logger
1143
+ executeUSyncQuery,
1144
+ onWhatsApp
962
1145
  }
963
1146
  }
964
1147