@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
@@ -12,11 +12,15 @@ const Utils_1 = require('../Utils')
12
12
  const browser_utils_1 = require('../Utils/browser-utils')
13
13
  const WABinary_1 = require('../WABinary')
14
14
  const BinaryInfo_js_1 = require('../WAM/BinaryInfo.js')
15
- const WAM_1 = require('../WAM')
16
15
  const WAUSync_1 = require('../WAUSync/')
17
16
  const Client_1 = require('./Client')
18
17
  const mex_1 = require('./mex')
19
-
18
+ /**
19
+ * Connects to WA servers and performs:
20
+ * - simple queries (no retry mechanism, wait for connection establishment)
21
+ * - listen to messages and emit events
22
+ * - query phone connection
23
+ */
20
24
  const makeSocket = config => {
21
25
  const {
22
26
  waWebSocketUrl,
@@ -57,9 +61,9 @@ const makeSocket = config => {
57
61
  if (url.protocol === 'wss' && authState?.creds?.routingInfo) {
58
62
  url.searchParams.append('ED', authState.creds.routingInfo.toString('base64url'))
59
63
  }
60
-
64
+ /** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
61
65
  const ephemeralKeyPair = Utils_1.Curve.generateKeyPair()
62
-
66
+ /** WA noise protocol wrapper */
63
67
  const noise = (0, Utils_1.makeNoiseHandler)({
64
68
  keyPair: ephemeralKeyPair,
65
69
  NOISE_HEADER: Defaults_1.NOISE_WA_HEADER,
@@ -67,10 +71,9 @@ const makeSocket = config => {
67
71
  routingInfo: authState?.creds?.routingInfo
68
72
  })
69
73
  const ws = new Client_1.WebSocketClient(url, config)
70
- const connectionStartTs = Date.now()
71
74
  ws.connect()
72
75
  const sendPromise = (0, util_1.promisify)(ws.send)
73
-
76
+ /** send a raw buffer */
74
77
  const sendRawMessage = async data => {
75
78
  if (!ws.isOpen) {
76
79
  throw new boom_1.Boom('Connection Closed', { statusCode: Types_1.DisconnectReason.connectionClosed })
@@ -85,7 +88,7 @@ const makeSocket = config => {
85
88
  }
86
89
  })
87
90
  }
88
-
91
+ /** send a binary node */
89
92
  const sendNode = frame => {
90
93
  if (logger.level === 'trace') {
91
94
  logger.trace({ xml: (0, WABinary_1.binaryNodeToString)(frame), msg: 'xml send' })
@@ -93,7 +96,11 @@ const makeSocket = config => {
93
96
  const buff = (0, WABinary_1.encodeBinaryNode)(frame)
94
97
  return sendRawMessage(buff)
95
98
  }
96
-
99
+ /**
100
+ * Wait for a message with a certain tag to be received
101
+ * @param msgId the message tag to await
102
+ * @param timeoutMs timeout after which the promise will reject
103
+ */
97
104
  const waitForMessage = async (msgId, timeoutMs = defaultQueryTimeoutMs) => {
98
105
  let onRecv
99
106
  let onErr
@@ -117,7 +124,7 @@ const makeSocket = config => {
117
124
  })
118
125
  return result
119
126
  } catch (error) {
120
-
127
+ // Catch timeout and return undefined instead of throwing
121
128
  if (error instanceof boom_1.Boom && error.output?.statusCode === Types_1.DisconnectReason.timedOut) {
122
129
  logger?.warn?.({ msgId }, 'timed out waiting for message')
123
130
  return undefined
@@ -131,7 +138,7 @@ const makeSocket = config => {
131
138
  }
132
139
  }
133
140
  }
134
-
141
+ /** send a query, and wait for its response. auto-generates message ID if not provided */
135
142
  const query = async (node, timeoutMs) => {
136
143
  if (!node.attrs.id) {
137
144
  node.attrs.id = generateMessageTag()
@@ -148,7 +155,7 @@ const makeSocket = config => {
148
155
  }
149
156
  return result
150
157
  }
151
-
158
+ // Validate current key-bundle on server; on failure, trigger pre-key upload and rethrow
152
159
  const digestKeyBundle = async () => {
153
160
  const res = await query({
154
161
  tag: 'iq',
@@ -161,7 +168,7 @@ const makeSocket = config => {
161
168
  throw new Error('encrypt/get digest returned no digest node')
162
169
  }
163
170
  }
164
-
171
+ // Rotate our signed pre-key on server; on failure, run digest as fallback and rethrow
165
172
  const rotateSignedPreKey = async () => {
166
173
  const newId = (creds.signedPreKey.keyId || 0) + 1
167
174
  const skey = await (0, Utils_1.signedKeyPair)(creds.signedIdentityKey, newId)
@@ -176,23 +183,21 @@ const makeSocket = config => {
176
183
  }
177
184
  ]
178
185
  })
179
-
186
+ // Persist new signed pre-key in creds
180
187
  ev.emit('creds.update', { signedPreKey: skey })
181
188
  }
182
189
  const executeUSyncQuery = async usyncQuery => {
183
190
  if (usyncQuery.protocols.length === 0) {
184
191
  throw new boom_1.Boom('USyncQuery must have at least one protocol')
185
192
  }
186
- const validUsers = usyncQuery.users.filter(user => (typeof user.validate === 'function' ? user.validate() : true))
187
- if (!validUsers.length) {
188
- logger.warn('a usync query must have at least one valid user')
189
- }
193
+ // todo: validate users, throw WARNING on no valid users
194
+ // variable below has only validated users
195
+ const validUsers = usyncQuery.users
190
196
  const userNodes = validUsers.map(user => {
191
197
  return {
192
198
  tag: 'user',
193
199
  attrs: {
194
- jid: !user.phone ? user.id : undefined,
195
- pn_jid: user.pnJid
200
+ jid: !user.phone ? user.id : undefined
196
201
  },
197
202
  content: usyncQuery.protocols.map(a => a.getUserElement(user)).filter(a => a !== null)
198
203
  }
@@ -228,17 +233,8 @@ const makeSocket = config => {
228
233
  }
229
234
  ]
230
235
  }
231
- await (0, WAUSync_1.waitForBackoff)(usyncQuery)
232
236
  const result = await query(iq)
233
- const parsed = usyncQuery.parseUSyncQueryResult(result)
234
- if (parsed?.errors) {
235
- for (const [protocolName, err] of Object.entries(parsed.errors)) {
236
- if (err?.errorBackoff) {
237
- ;(0, WAUSync_1.setProtocolBackoffMs)(protocolName, err.errorBackoff * 1000)
238
- }
239
- }
240
- }
241
- return parsed
237
+ return usyncQuery.parseUSyncQueryResult(result)
242
238
  }
243
239
  const onWhatsApp = async (...phoneNumber) => {
244
240
  let usyncQuery = new WAUSync_1.USyncQuery()
@@ -257,7 +253,7 @@ const makeSocket = config => {
257
253
  }
258
254
  }
259
255
  if (usyncQuery.users.length === 0) {
260
- return []
256
+ return [] // return early without forcing an empty query
261
257
  }
262
258
  const results = await executeUSyncQuery(usyncQuery)
263
259
  if (results) {
@@ -275,7 +271,7 @@ const makeSocket = config => {
275
271
  }
276
272
  }
277
273
  if (usyncQuery.users.length === 0) {
278
- return []
274
+ return [] // return early without forcing an empty query
279
275
  }
280
276
  const results = await executeUSyncQuery(usyncQuery)
281
277
  if (results) {
@@ -283,9 +279,83 @@ const makeSocket = config => {
283
279
  }
284
280
  return []
285
281
  }
282
+ /**
283
+ * @param {string | string[]} lid - @lid JID or array of @lid JID
284
+ * @returns {Promise<{ lid: string, pn: string | null }[]>}
285
+ */
286
+ const toPn = async lid => {
287
+ const lids = Array.isArray(lid) ? lid : [lid]
288
+ const usyncQuery = new WAUSync_1.USyncQuery().withContactProtocol().withLIDProtocol()
289
+ for (const l of lids) {
290
+ const decoded = (0, WABinary_1.jidDecode)(l)
291
+ if (!decoded) continue
292
+ usyncQuery.withUser(
293
+ new WAUSync_1.USyncUser().withId((0, WABinary_1.jidEncode)(decoded.user, 's.whatsapp.net')).withLid(l)
294
+ )
295
+ }
296
+ const result = await executeUSyncQuery(usyncQuery)
297
+ return lids.map(l => {
298
+ const decoded = (0, WABinary_1.jidDecode)(l)
299
+ if (!decoded) return { lid: l, pn: null }
300
+ const match = result?.list?.find(entry => {
301
+ const entryDecoded = (0, WABinary_1.jidDecode)(entry.id)
302
+ return entryDecoded?.user === decoded.user
303
+ })
304
+ if (!match?.contact) return { lid: l, pn: null }
305
+ const pn = (0, WABinary_1.jidEncode)((0, WABinary_1.jidDecode)(match.id)?.user, 's.whatsapp.net')
306
+ return { lid: l, pn: pn ?? null }
307
+ })
308
+ }
309
+ /**
310
+ * @param {string | string[]} pn - @s.whatsapp.net JID or array of @s.whatsapp.net JID
311
+ * @returns {Promise<{ pn: string, lid: string | null }[]>}
312
+ */
313
+ const toLID = async pn => {
314
+ const pns = Array.isArray(pn) ? pn : [pn]
315
+ const usyncQuery = new WAUSync_1.USyncQuery().withContactProtocol().withLIDProtocol()
316
+ for (const p of pns) {
317
+ const decoded = (0, WABinary_1.jidDecode)(p)
318
+ if (!decoded) continue
319
+ usyncQuery.withUser(new WAUSync_1.USyncUser().withId((0, WABinary_1.jidEncode)(decoded.user, 's.whatsapp.net')))
320
+ }
321
+ const result = await executeUSyncQuery(usyncQuery)
322
+ return pns.map(p => {
323
+ const decoded = (0, WABinary_1.jidDecode)(p)
324
+ if (!decoded) return { pn: p, lid: null }
325
+ const match = result?.list?.find(entry => {
326
+ return (0, WABinary_1.jidDecode)(entry.id)?.user === decoded.user
327
+ })
328
+ if (!match?.contact) return { pn: p, lid: null }
329
+ const lidVal = match?.lid ?? null
330
+ const lid = lidVal ? (0, WABinary_1.jidEncode)((0, WABinary_1.jidDecode)(lidVal)?.user ?? lidVal, 'lid') : null
331
+ return { pn: p, lid }
332
+ })
333
+ }
334
+ /**
335
+ * @param {string | string[]} username - username to fetch
336
+ * @param {string} [pin] - pin of username if the user has one set
337
+ * @returns {Promise<{ username: string, pn: string | null, lid: string | null }[]>}
338
+ */
339
+ const fetchUsername = async (username, pin) => {
340
+ const usernames = Array.isArray(username) ? username : [username]
341
+ const usyncQuery = new WAUSync_1.USyncQuery().withContactProtocol().withLIDProtocol()
342
+ for (const u of usernames) {
343
+ const user = new WAUSync_1.USyncUser().withUsername(u)
344
+ if (pin) user.withUsernameKey(pin)
345
+ usyncQuery.withUser(user)
346
+ }
347
+ const result = await executeUSyncQuery(usyncQuery)
348
+ return usernames.map(u => {
349
+ const match = result?.list?.find(entry => entry?.username === u)
350
+ if (!match?.contact) return { username: u, pn: null, lid: null }
351
+ const lid = match.lid
352
+ const pn = (0, WABinary_1.jidEncode)((0, WABinary_1.jidDecode)(match.id)?.user, 's.whatsapp.net')
353
+ return { username: u, pn: pn ?? null, lid: lid ?? null }
354
+ })
355
+ }
286
356
  const ev = (0, Utils_1.makeEventBuffer)(logger)
287
357
  const { creds } = authState
288
-
358
+ // add transaction capability
289
359
  const keys = (0, Utils_1.addTransactionCapability)(authState.keys, logger, transactionOpts)
290
360
  const signalRepository = makeSignalRepository({ creds, keys }, logger, pnFromLIDUSync)
291
361
  let lastDateRecv
@@ -293,11 +363,12 @@ const makeSocket = config => {
293
363
  let keepAliveReq
294
364
  let qrTimer
295
365
  let closed = false
296
-
366
+ const socketEndHandlers = []
367
+ /** log & process any unexpected errors */
297
368
  const onUnexpectedError = (err, msg) => {
298
369
  logger.error({ err }, `unexpected error in '${msg}'`)
299
370
  }
300
-
371
+ /** await the next incoming message */
301
372
  const awaitNextMessage = async sendMsg => {
302
373
  if (!ws.isOpen) {
303
374
  throw new boom_1.Boom('Connection Closed', {
@@ -322,7 +393,7 @@ const makeSocket = config => {
322
393
  }
323
394
  return result
324
395
  }
325
-
396
+ /** connection handshake */
326
397
  const validateConnection = async () => {
327
398
  let helloMsg = {
328
399
  clientHello: { ephemeral: ephemeralKeyPair.public }
@@ -333,7 +404,7 @@ const makeSocket = config => {
333
404
  const result = await awaitNextMessage(init)
334
405
  const handshake = index_js_1.proto.HandshakeMessage.decode(result)
335
406
  logger.trace({ handshake }, 'handshake recv from WA')
336
- const { keyEnc, serverStaticPub } = noise.processHandshake(handshake, creds.noiseKey)
407
+ const keyEnc = noise.processHandshake(handshake, creds.noiseKey)
337
408
  let node
338
409
  if (!creds.me) {
339
410
  node = (0, Utils_1.generateRegistrationNode)(creds, config)
@@ -352,9 +423,6 @@ const makeSocket = config => {
352
423
  }).finish()
353
424
  )
354
425
  await noise.finishInit()
355
- if (serverStaticPub && !creds.serverStaticPub) {
356
- ev.emit('creds.update', { serverStaticPub })
357
- }
358
426
  startKeepAliveRequest()
359
427
  }
360
428
  const getAvailablePreKeysOnServer = async () => {
@@ -371,12 +439,12 @@ const makeSocket = config => {
371
439
  const countChild = (0, WABinary_1.getBinaryNodeChild)(result, 'count')
372
440
  return +countChild.attrs.value
373
441
  }
374
-
442
+ // Pre-key upload state management
375
443
  let uploadPreKeysPromise = null
376
444
  let lastUploadTime = 0
377
-
445
+ /** generates and uploads a set of pre-keys to the server */
378
446
  const uploadPreKeys = async (count = Defaults_1.MIN_PREKEY_COUNT, retryCount = 0) => {
379
-
447
+ // Check minimum interval (except for retries)
380
448
  if (retryCount === 0) {
381
449
  const timeSinceLastUpload = Date.now() - lastUploadTime
382
450
  if (timeSinceLastUpload < Defaults_1.MIN_UPLOAD_INTERVAL) {
@@ -384,29 +452,29 @@ const makeSocket = config => {
384
452
  return
385
453
  }
386
454
  }
387
-
455
+ // Prevent multiple concurrent uploads
388
456
  if (uploadPreKeysPromise) {
389
457
  logger.debug('Pre-key upload already in progress, waiting for completion')
390
458
  await uploadPreKeysPromise
391
459
  }
392
460
  const uploadLogic = async () => {
393
461
  logger.info({ count, retryCount }, 'uploading pre-keys')
394
-
462
+ // Generate and save pre-keys atomically (prevents ID collisions on retry)
395
463
  const node = await keys.transaction(async () => {
396
464
  logger.debug({ requestedCount: count }, 'generating pre-keys with requested count')
397
465
  const { update, node } = await (0, Utils_1.getNextPreKeysNode)({ creds, keys }, count)
398
-
466
+ // Update credentials immediately to prevent duplicate IDs on retry
399
467
  ev.emit('creds.update', update)
400
- return node
468
+ return node // Only return node since update is already used
401
469
  }, creds?.me?.id || 'upload-pre-keys')
402
-
470
+ // Upload to server (outside transaction, can fail without affecting local keys)
403
471
  try {
404
472
  await query(node)
405
473
  logger.info({ count }, 'uploaded pre-keys successfully')
406
474
  lastUploadTime = Date.now()
407
475
  } catch (uploadError) {
408
476
  logger.error({ uploadError: uploadError.toString(), count }, 'Failed to upload pre-keys to server')
409
-
477
+ // Exponential backoff retry (max 3 retries)
410
478
  if (retryCount < 3) {
411
479
  const backoffDelay = Math.min(1000 * Math.pow(2, retryCount), 10000)
412
480
  logger.info(`Retrying pre-key upload in ${backoffDelay}ms`)
@@ -416,7 +484,7 @@ const makeSocket = config => {
416
484
  throw uploadError
417
485
  }
418
486
  }
419
-
487
+ // Add timeout protection
420
488
  uploadPreKeysPromise = Promise.race([
421
489
  uploadLogic(),
422
490
  new Promise((_, reject) =>
@@ -464,47 +532,40 @@ const makeSocket = config => {
464
532
  }
465
533
  } catch (error) {
466
534
  logger.error({ error }, 'Failed to check/upload pre-keys during initialization')
467
-
535
+ // Don't throw - allow connection to continue even if pre-key check fails
468
536
  }
469
537
  }
470
538
  const onMessageReceived = async data => {
471
- try {
472
- await noise.decodeFrame(data, frame => {
473
-
474
- lastDateRecv = new Date()
475
- let anyTriggered = false
476
- anyTriggered = ws.emit('frame', frame)
477
-
478
- if (!(frame instanceof Uint8Array)) {
479
- const msgId = frame.attrs.id
480
- if (logger.level === 'trace') {
481
- logger.trace({ xml: (0, WABinary_1.binaryNodeToString)(frame), msg: 'recv xml' })
482
- }
483
-
484
- anyTriggered = ws.emit(`${Defaults_1.DEF_TAG_PREFIX}${msgId}`, frame) || anyTriggered
485
-
486
- const l0 = frame.tag
487
- const l1 = frame.attrs || {}
488
- const l2 = Array.isArray(frame.content) ? frame.content[0]?.tag : ''
489
- for (const key of Object.keys(l1)) {
490
- anyTriggered =
491
- ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]},${l2}`, frame) || anyTriggered
492
- anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]}`, frame) || anyTriggered
493
- anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}`, frame) || anyTriggered
494
- }
495
- anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},,${l2}`, frame) || anyTriggered
496
- anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0}`, frame) || anyTriggered
497
- if (!anyTriggered && logger.level === 'debug') {
498
- logger.debug({ unhandled: true, msgId, fromMe: false, frame }, 'communication recv')
499
- }
539
+ await noise.decodeFrame(data, frame => {
540
+ // reset ping timeout
541
+ lastDateRecv = new Date()
542
+ let anyTriggered = false
543
+ anyTriggered = ws.emit('frame', frame)
544
+ // if it's a binary node
545
+ if (!(frame instanceof Uint8Array)) {
546
+ const msgId = frame.attrs.id
547
+ if (logger.level === 'trace') {
548
+ logger.trace({ xml: (0, WABinary_1.binaryNodeToString)(frame), msg: 'recv xml' })
500
549
  }
501
- })
502
- } catch (error) {
503
-
504
-
505
- logger.error({ error }, 'error decoding frame')
506
- void end(error instanceof Error ? error : new Error(String(error)))
507
- }
550
+ /* Check if this is a response to a message we sent */
551
+ anyTriggered = ws.emit(`${Defaults_1.DEF_TAG_PREFIX}${msgId}`, frame) || anyTriggered
552
+ /* Check if this is a response to a message we are expecting */
553
+ const l0 = frame.tag
554
+ const l1 = frame.attrs || {}
555
+ const l2 = Array.isArray(frame.content) ? frame.content[0]?.tag : ''
556
+ for (const key of Object.keys(l1)) {
557
+ anyTriggered =
558
+ ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]},${l2}`, frame) || anyTriggered
559
+ anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]}`, frame) || anyTriggered
560
+ anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}`, frame) || anyTriggered
561
+ }
562
+ anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},,${l2}`, frame) || anyTriggered
563
+ anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0}`, frame) || anyTriggered
564
+ if (!anyTriggered && logger.level === 'debug') {
565
+ logger.debug({ unhandled: true, msgId, fromMe: false, frame }, 'communication recv')
566
+ }
567
+ }
568
+ })
508
569
  }
509
570
  const end = async error => {
510
571
  if (closed) {
@@ -514,7 +575,6 @@ const makeSocket = config => {
514
575
  closed = true
515
576
  logger.info({ trace: error?.stack }, error ? 'connection errored' : 'connection closed')
516
577
  clearInterval(keepAliveReq)
517
- clearInterval(wamFlushInterval)
518
578
  clearTimeout(qrTimer)
519
579
  ws.removeAllListeners('close')
520
580
  ws.removeAllListeners('open')
@@ -524,6 +584,13 @@ const makeSocket = config => {
524
584
  await ws.close()
525
585
  } catch {}
526
586
  }
587
+ for (const handler of socketEndHandlers) {
588
+ try {
589
+ await handler(error)
590
+ } catch (err) {
591
+ logger.error({ err }, 'error in socket end handler')
592
+ }
593
+ }
527
594
  ev.emit('connection.update', {
528
595
  connection: 'close',
529
596
  lastDisconnect: {
@@ -560,13 +627,14 @@ const makeSocket = config => {
560
627
  lastDateRecv = new Date()
561
628
  }
562
629
  const diff = Date.now() - lastDateRecv.getTime()
563
-
630
+ /*
631
+ check if it's been a suspicious amount of time since the server responded with our last seen
632
+ it could be that the network is down
633
+ */
564
634
  if (diff > keepAliveIntervalMs + 5000) {
565
635
  void end(new boom_1.Boom('Connection was lost', { statusCode: Types_1.DisconnectReason.connectionLost }))
566
636
  } else if (ws.isOpen) {
567
-
568
-
569
-
637
+ // if its all good, send a keep alive request
570
638
  query({
571
639
  tag: 'iq',
572
640
  attrs: {
@@ -574,7 +642,8 @@ const makeSocket = config => {
574
642
  to: WABinary_1.S_WHATSAPP_NET,
575
643
  type: 'get',
576
644
  xmlns: 'w:p'
577
- }
645
+ },
646
+ content: [{ tag: 'ping', attrs: {} }]
578
647
  }).catch(err => {
579
648
  logger.error({ trace: err.stack }, 'error in sending keep alive')
580
649
  })
@@ -582,7 +651,7 @@ const makeSocket = config => {
582
651
  logger.warn('keep alive called when WS not open')
583
652
  }
584
653
  }, keepAliveIntervalMs))
585
-
654
+ /** i have no idea why this exists. pls enlighten me */
586
655
  const sendPassiveIq = tag =>
587
656
  query({
588
657
  tag: 'iq',
@@ -593,7 +662,7 @@ const makeSocket = config => {
593
662
  },
594
663
  content: [{ tag, attrs: {} }]
595
664
  })
596
-
665
+ /** logout & invalidate connection */
597
666
  const logout = async msg => {
598
667
  const jid = authState.creds.me?.id
599
668
  if (jid) {
@@ -620,7 +689,7 @@ const makeSocket = config => {
620
689
  }
621
690
  const requestPairingCode = async (phoneNumber, customPairingCode) => {
622
691
  const pairingCode = customPairingCode ?? (0, Utils_1.bytesToCrockford)((0, crypto_1.randomBytes)(5))
623
- if (customPairingCode && customPairingCode.length !== 8) {
692
+ if (customPairingCode && customPairingCode?.length !== 8) {
624
693
  throw new Error('Custom pairing code must be exactly 8 chars')
625
694
  }
626
695
  authState.creds.pairingCode = pairingCode
@@ -690,8 +759,7 @@ const makeSocket = config => {
690
759
  attrs: {
691
760
  to: WABinary_1.S_WHATSAPP_NET,
692
761
  id: generateMessageTag(),
693
- xmlns: 'w:stats',
694
- type: 'set'
762
+ xmlns: 'w:stats'
695
763
  },
696
764
  content: [
697
765
  {
@@ -702,30 +770,8 @@ const makeSocket = config => {
702
770
  ]
703
771
  })
704
772
  }
705
- const flushWAMEvents = async () => {
706
- if (!publicWAMBuffer.events.length) return
707
- try {
708
- const encoded = (0, WAM_1.encodeWAM)(publicWAMBuffer)
709
- publicWAMBuffer.events = []
710
- publicWAMBuffer.sequence = (publicWAMBuffer.sequence + 1) & 0xffff
711
- await sendWAMBuffer(encoded)
712
- } catch (e) {
713
- logger.debug({ err: e }, 'WAM flush failed')
714
- }
715
- }
716
- const wsOpenTs = { value: connectionStartTs }
717
773
  ws.on('message', onMessageReceived)
718
774
  ws.on('open', async () => {
719
- wsOpenTs.value = Date.now()
720
- publicWAMBuffer.events.push({
721
- WebcSocketConnect: {
722
- props: {
723
- webcSocketConnectDuration: wsOpenTs.value - connectionStartTs,
724
- webcSocketConnectReason: 0
725
- },
726
- globals: {}
727
- }
728
- })
729
775
  try {
730
776
  await validateConnection()
731
777
  } catch (err) {
@@ -733,15 +779,12 @@ const makeSocket = config => {
733
779
  void end(err)
734
780
  }
735
781
  })
736
- const wamFlushInterval = setInterval(() => {
737
- void flushWAMEvents()
738
- }, 30_000)
739
782
  ws.on('error', mapWebSocketError(end))
740
783
  ws.on(
741
784
  'close',
742
785
  () => void end(new boom_1.Boom('Connection Terminated', { statusCode: Types_1.DisconnectReason.connectionClosed }))
743
786
  )
744
-
787
+ // the server terminated the connection
745
788
  ws.on(
746
789
  'CB:xmlstreamend',
747
790
  () =>
@@ -749,7 +792,7 @@ const makeSocket = config => {
749
792
  new boom_1.Boom('Connection Terminated by Server', { statusCode: Types_1.DisconnectReason.connectionClosed })
750
793
  )
751
794
  )
752
-
795
+ // QR gen
753
796
  ws.on('CB:iq,type:set,pair-device', async stanza => {
754
797
  const iq = {
755
798
  tag: 'iq',
@@ -765,7 +808,7 @@ const makeSocket = config => {
765
808
  const noiseKeyB64 = Buffer.from(creds.noiseKey.public).toString('base64')
766
809
  const identityKeyB64 = Buffer.from(creds.signedIdentityKey.public).toString('base64')
767
810
  const advB64 = creds.advSecretKey
768
- let qrMs = qrTimeout || 60000
811
+ let qrMs = qrTimeout || 60000 // time to let a QR live
769
812
  const genPairQR = () => {
770
813
  if (!ws.isOpen) {
771
814
  return
@@ -775,16 +818,16 @@ const makeSocket = config => {
775
818
  void end(new boom_1.Boom('QR refs attempts ended', { statusCode: Types_1.DisconnectReason.timedOut }))
776
819
  return
777
820
  }
778
- const ref = refNode.content.toString('utf8')
821
+ const ref = refNode.content.toString('utf-8')
779
822
  const qr = [ref, noiseKeyB64, identityKeyB64, advB64].join(',')
780
823
  ev.emit('connection.update', { qr })
781
824
  qrTimer = setTimeout(genPairQR, qrMs)
782
- qrMs = qrTimeout || 20000
825
+ qrMs = qrTimeout || 20000 // shorter subsequent qrs
783
826
  }
784
827
  genPairQR()
785
828
  })
786
-
787
-
829
+ // device paired for the first time
830
+ // if device pairs successfully, the server asks to restart the connection
788
831
  ws.on('CB:iq,,pair-success', async stanza => {
789
832
  logger.debug('pair success recv')
790
833
  try {
@@ -803,16 +846,16 @@ const makeSocket = config => {
803
846
  void end(error)
804
847
  }
805
848
  })
806
-
849
+ // login complete
807
850
  ws.on('CB:success', async node => {
808
851
  try {
809
852
  updateServerTimeOffset(node)
810
853
  await uploadPreKeysToServerIfRequired()
811
-
854
+ // passive→active handshake only for companion (passive) sessions
812
855
  if (!config.masqueradeAsPrimary) {
813
856
  await sendPassiveIq('active')
814
857
  }
815
-
858
+ // After successful login, validate our key-bundle against server
816
859
  try {
817
860
  await digestKeyBundle()
818
861
  } catch (e) {
@@ -822,15 +865,7 @@ const makeSocket = config => {
822
865
  logger.warn({ err }, 'failed to send initial passive iq')
823
866
  }
824
867
  logger.info('opened connection to WA')
825
- clearTimeout(qrTimer)
826
-
827
- publicWAMBuffer.events.push({
828
- Login: {
829
- props: { loginResult: 1, loginT: Date.now() - connectionStartTs, connectionOrigin: 1, passive: false },
830
- globals: {}
831
- }
832
- })
833
- void flushWAMEvents()
868
+ clearTimeout(qrTimer) // will never happen in all likelyhood -- but just in case WA sends success on first try
834
869
  ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } })
835
870
  if (config.syncFullHistory && authState.creds.initialFullSyncDone !== true) {
836
871
  logger.debug('initial full history sync completed, persisting one-time flag')
@@ -843,16 +878,16 @@ const makeSocket = config => {
843
878
  process.nextTick(async () => {
844
879
  try {
845
880
  const myPN = authState.creds.me.id
846
-
881
+ // Store our own LID-PN mapping
847
882
  await signalRepository.lidMapping.storeLIDPNMappings([{ lid: myLID, pn: myPN }])
848
-
883
+ // Create device list for our own user (needed for bulk migration)
849
884
  const { user, device } = (0, WABinary_1.jidDecode)(myPN)
850
885
  await authState.keys.set({
851
886
  'device-list': {
852
887
  [user]: [device?.toString() || '0']
853
888
  }
854
889
  })
855
-
890
+ // migrate our own session
856
891
  await signalRepository.migrateSession(myPN, myLID)
857
892
  logger.info({ myPN, myLID }, 'Own LID session created successfully')
858
893
  } catch (error) {
@@ -867,7 +902,7 @@ const makeSocket = config => {
867
902
  const { reason, statusCode } = (0, Utils_1.getErrorCodeFromStreamError)(node)
868
903
  void end(new boom_1.Boom(`Stream Errored (${reason})`, { statusCode, data: reasonNode || node }))
869
904
  })
870
-
905
+ // stream fail, possible logout
871
906
  ws.on('CB:failure', node => {
872
907
  const reason = +(node.attrs.reason || 500)
873
908
  void end(new boom_1.Boom('Connection Failure', { statusCode: reason, data: node.attrs }))
@@ -877,15 +912,6 @@ const makeSocket = config => {
877
912
  new boom_1.Boom('Multi-device beta not joined', { statusCode: Types_1.DisconnectReason.multideviceMismatch })
878
913
  )
879
914
  })
880
- ws.on('CB:ib,,dirty', node => {
881
- const dirtyNode = (0, WABinary_1.getBinaryNodeChild)(node, 'dirty')
882
- const dirtyType = dirtyNode?.attrs?.type
883
- const dirtyTs = dirtyNode?.attrs?.timestamp ? +dirtyNode.attrs.timestamp : undefined
884
-
885
-
886
- logger.debug({ dirtyType, dirtyTs }, 'received dirty flag')
887
- ev.emit('account.dirty', { type: dirtyType, timestamp: dirtyTs })
888
- })
889
915
  ws.on('CB:ib,,offline_preview', async node => {
890
916
  logger.info('offline preview received', JSON.stringify(node))
891
917
  await sendNode({
@@ -894,19 +920,6 @@ const makeSocket = config => {
894
920
  content: [{ tag: 'offline_batch', attrs: { count: '100' } }]
895
921
  })
896
922
  })
897
- ws.on('CB:ib,,thread_metadata', node => {
898
- const tmNode = (0, WABinary_1.getBinaryNodeChild)(node, 'thread_metadata')
899
- const items = (0, WABinary_1.getBinaryNodeChildren)(tmNode, 'item')
900
- if (items.length) {
901
- ev.emit(
902
- 'thread-metadata.update',
903
- items.map(item => ({
904
- jid: item.attrs.from,
905
- t: item.attrs.t ? +item.attrs.t : undefined
906
- }))
907
- )
908
- }
909
- })
910
923
  ws.on('CB:ib,,edge_routing', node => {
911
924
  const edgeRoutingNode = (0, WABinary_1.getBinaryNodeChild)(node, 'edge_routing')
912
925
  const routingInfo = (0, WABinary_1.getBinaryNodeChild)(edgeRoutingNode, 'routing_info')
@@ -918,14 +931,14 @@ const makeSocket = config => {
918
931
  let didStartBuffer = false
919
932
  process.nextTick(() => {
920
933
  if (creds.me?.id) {
921
-
922
-
934
+ // start buffering important events
935
+ // if we're logged in
923
936
  ev.buffer()
924
937
  didStartBuffer = true
925
938
  }
926
939
  ev.emit('connection.update', { connection: 'connecting', receivedPendingNotifications: false, qr: undefined })
927
940
  })
928
-
941
+ // called when all offline notifs are handled
929
942
  ws.on('CB:ib,,offline', node => {
930
943
  const child = (0, WABinary_1.getBinaryNodeChild)(node, 'offline')
931
944
  const offlineNotifs = +(child?.attrs.count || 0)
@@ -936,14 +949,11 @@ const makeSocket = config => {
936
949
  }
937
950
  ev.emit('connection.update', { receivedPendingNotifications: true })
938
951
  })
939
-
952
+ // update credentials when required
940
953
  ev.on('creds.update', update => {
941
954
  const name = update.me?.name
942
-
943
-
944
-
945
-
946
- if (typeof name === 'string' && name.length > 0 && creds.me?.name !== name) {
955
+ // if name has just been received
956
+ if (creds.me?.name !== name) {
947
957
  logger.debug({ name }, 'updated pushName')
948
958
  sendNode({
949
959
  tag: 'presence',
@@ -1023,6 +1033,9 @@ const makeSocket = config => {
1023
1033
  generateMessageTag
1024
1034
  )
1025
1035
  }
1036
+ const registerSocketEndHandler = handler => {
1037
+ socketEndHandlers.push(handler)
1038
+ }
1026
1039
  return {
1027
1040
  type: 'md',
1028
1041
  ws,
@@ -1040,6 +1053,10 @@ const makeSocket = config => {
1040
1053
  sendNode,
1041
1054
  logout,
1042
1055
  end,
1056
+ registerSocketEndHandler,
1057
+ toPn,
1058
+ toLID,
1059
+ fetchUsername,
1043
1060
  onUnexpectedError,
1044
1061
  uploadPreKeys,
1045
1062
  uploadPreKeysToServerIfRequired,
@@ -1049,19 +1066,20 @@ const makeSocket = config => {
1049
1066
  updateServerTimeOffset,
1050
1067
  sendUnifiedSession,
1051
1068
  wamBuffer: publicWAMBuffer,
1052
-
1069
+ /** Waits for the connection to WA to reach a state */
1053
1070
  waitForConnectionUpdate: (0, Utils_1.bindWaitForConnectionUpdate)(ev),
1054
1071
  sendWAMBuffer,
1055
1072
  executeUSyncQuery,
1056
1073
  onWhatsApp,
1057
1074
  fetchAccountReachoutTimelock,
1058
- fetchNewChatMessageCap,
1059
- logger,
1060
- masqueradeAsPrimary: config.masqueradeAsPrimary
1075
+ fetchNewChatMessageCap
1061
1076
  }
1062
1077
  }
1063
1078
  exports.makeSocket = makeSocket
1064
-
1079
+ /**
1080
+ * map the websocket error to the right type
1081
+ * so it can be retried by the caller
1082
+ * */
1065
1083
  function mapWebSocketError(handler) {
1066
1084
  return error => {
1067
1085
  handler(