@badzz88/baileys 8.5.3 → 8.5.4

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 (110) hide show
  1. package/README.md +1 -1
  2. package/WAProto/WAProto.proto +1256 -75
  3. package/WAProto/index.d.ts +36042 -19649
  4. package/WAProto/index.js +181990 -135645
  5. package/engine-requirements.js +7 -15
  6. package/lib/Defaults/index.js +22 -17
  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 +39 -59
  15. package/lib/Signal/lid-mapping.js +28 -10
  16. package/lib/Socket/aigroups.js +5 -5
  17. package/lib/Socket/business.js +3 -11
  18. package/lib/Socket/chats.js +668 -319
  19. package/lib/Socket/communities.js +85 -89
  20. package/lib/Socket/graphql.js +435 -228
  21. package/lib/Socket/groups.js +132 -211
  22. package/lib/Socket/index.js +9 -7
  23. package/lib/Socket/interactive-handler.js +117 -60
  24. package/lib/Socket/interop.js +56 -175
  25. package/lib/Socket/managed-account.js +79 -48
  26. package/lib/Socket/messages-recv.js +898 -340
  27. package/lib/Socket/messages-send.js +462 -160
  28. package/lib/Socket/mex.js +40 -53
  29. package/lib/Socket/newsletter.js +291 -295
  30. package/lib/Socket/privacy.js +77 -265
  31. package/lib/Socket/registration.js +164 -157
  32. package/lib/Socket/socket.js +181 -199
  33. package/lib/Socket/username.js +144 -227
  34. package/lib/Store/make-cache-manager-store.js +2 -2
  35. package/lib/Store/make-in-memory-store.js +25 -141
  36. package/lib/Types/Auth.js +3 -3
  37. package/lib/Types/Label.js +1 -1
  38. package/lib/Types/LabelAssociation.js +1 -1
  39. package/lib/Types/Newsletter.js +96 -65
  40. package/lib/Types/State.js +4 -4
  41. package/lib/Types/index.js +1 -1
  42. package/lib/Utils/auth-utils.js +52 -57
  43. package/lib/Utils/browser-utils.js +1 -1
  44. package/lib/Utils/business.js +2 -6
  45. package/lib/Utils/chat-utils.js +102 -38
  46. package/lib/Utils/crypto.js +54 -84
  47. package/lib/Utils/decode-wa-message.js +316 -83
  48. package/lib/Utils/event-buffer.js +37 -61
  49. package/lib/Utils/generics.js +157 -103
  50. package/lib/Utils/group-history.js +3 -12
  51. package/lib/Utils/history.js +15 -15
  52. package/lib/Utils/index.js +1 -5
  53. package/lib/Utils/link-preview.js +3 -8
  54. package/lib/Utils/lt-hash.js +2 -35
  55. package/lib/Utils/make-mutex.js +1 -1
  56. package/lib/Utils/message-inspect.js +393 -0
  57. package/lib/Utils/message-retry-manager.js +27 -92
  58. package/lib/Utils/messages-media.js +159 -130
  59. package/lib/Utils/messages.js +144 -566
  60. package/lib/Utils/meta-ai-msmsg.js +62 -202
  61. package/lib/Utils/noise-handler.js +19 -21
  62. package/lib/Utils/offline-node-processor.js +3 -8
  63. package/lib/Utils/pre-key-manager.js +13 -23
  64. package/lib/Utils/process-message.js +197 -106
  65. package/lib/Utils/reporting-utils.js +2 -2
  66. package/lib/Utils/signal.js +28 -63
  67. package/lib/Utils/stanza-ack.js +39 -12
  68. package/lib/Utils/sync-action-utils.js +4 -7
  69. package/lib/Utils/tc-token-utils.js +20 -25
  70. package/lib/Utils/use-multi-file-auth-state.js +30 -14
  71. package/lib/Utils/validate-connection.js +59 -19
  72. package/lib/WABinary/decode.js +50 -16
  73. package/lib/WABinary/encode.js +54 -224
  74. package/lib/WABinary/generic-utils.js +7 -97
  75. package/lib/WABinary/jid-utils.js +27 -14
  76. package/lib/WAM/encode.js +7 -7
  77. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +58 -1
  78. package/lib/WAUSync/Protocols/USyncContactProtocol.js +7 -1
  79. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +13 -5
  80. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +1 -4
  81. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +20 -7
  82. package/lib/WAUSync/Protocols/USyncNewsletterProtocol.js +865 -0
  83. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +1 -1
  84. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +2 -2
  85. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +7 -2
  86. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +1 -1
  87. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +2 -1
  88. package/lib/WAUSync/Protocols/index.js +1 -0
  89. package/lib/WAUSync/USyncBackoff.js +26 -0
  90. package/lib/WAUSync/USyncQuery.js +77 -6
  91. package/lib/WAUSync/USyncUser.js +16 -8
  92. package/lib/WAUSync/index.js +1 -0
  93. package/lib/antiban.js +608 -854
  94. package/lib/index.js +1 -1
  95. package/package.json +16 -20
  96. package/lib/Socket/luxu.js +0 -387
  97. package/lib/Socket/text-router.js +0 -83
  98. package/lib/Store/keyed-db.js +0 -108
  99. package/lib/Types/Mex.js +0 -112
  100. package/lib/Utils/command-loader.d.ts +0 -31
  101. package/lib/Utils/command-loader.js +0 -100
  102. package/lib/Utils/companion-reg-client-utils.js +0 -42
  103. package/lib/Utils/curve25519-js.js +0 -275
  104. package/lib/Utils/native-bridge.js +0 -82
  105. package/lib/Utils/session-pool.d.ts +0 -16
  106. package/lib/Utils/session-pool.js +0 -94
  107. package/lib/Utils/sticker.d.ts +0 -17
  108. package/lib/Utils/sticker.js +0 -145
  109. package/lib/Utils/view-once-cache.d.ts +0 -9
  110. package/lib/Utils/view-once-cache.js +0 -79
@@ -12,15 +12,11 @@ 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')
15
16
  const WAUSync_1 = require('../WAUSync/')
16
17
  const Client_1 = require('./Client')
17
18
  const mex_1 = require('./mex')
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
- */
19
+
24
20
  const makeSocket = config => {
25
21
  const {
26
22
  waWebSocketUrl,
@@ -61,9 +57,9 @@ const makeSocket = config => {
61
57
  if (url.protocol === 'wss' && authState?.creds?.routingInfo) {
62
58
  url.searchParams.append('ED', authState.creds.routingInfo.toString('base64url'))
63
59
  }
64
- /** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
60
+
65
61
  const ephemeralKeyPair = Utils_1.Curve.generateKeyPair()
66
- /** WA noise protocol wrapper */
62
+
67
63
  const noise = (0, Utils_1.makeNoiseHandler)({
68
64
  keyPair: ephemeralKeyPair,
69
65
  NOISE_HEADER: Defaults_1.NOISE_WA_HEADER,
@@ -71,9 +67,10 @@ const makeSocket = config => {
71
67
  routingInfo: authState?.creds?.routingInfo
72
68
  })
73
69
  const ws = new Client_1.WebSocketClient(url, config)
70
+ const connectionStartTs = Date.now()
74
71
  ws.connect()
75
72
  const sendPromise = (0, util_1.promisify)(ws.send)
76
- /** send a raw buffer */
73
+
77
74
  const sendRawMessage = async data => {
78
75
  if (!ws.isOpen) {
79
76
  throw new boom_1.Boom('Connection Closed', { statusCode: Types_1.DisconnectReason.connectionClosed })
@@ -88,7 +85,7 @@ const makeSocket = config => {
88
85
  }
89
86
  })
90
87
  }
91
- /** send a binary node */
88
+
92
89
  const sendNode = frame => {
93
90
  if (logger.level === 'trace') {
94
91
  logger.trace({ xml: (0, WABinary_1.binaryNodeToString)(frame), msg: 'xml send' })
@@ -96,11 +93,7 @@ const makeSocket = config => {
96
93
  const buff = (0, WABinary_1.encodeBinaryNode)(frame)
97
94
  return sendRawMessage(buff)
98
95
  }
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
- */
96
+
104
97
  const waitForMessage = async (msgId, timeoutMs = defaultQueryTimeoutMs) => {
105
98
  let onRecv
106
99
  let onErr
@@ -124,7 +117,7 @@ const makeSocket = config => {
124
117
  })
125
118
  return result
126
119
  } catch (error) {
127
- // Catch timeout and return undefined instead of throwing
120
+
128
121
  if (error instanceof boom_1.Boom && error.output?.statusCode === Types_1.DisconnectReason.timedOut) {
129
122
  logger?.warn?.({ msgId }, 'timed out waiting for message')
130
123
  return undefined
@@ -138,7 +131,7 @@ const makeSocket = config => {
138
131
  }
139
132
  }
140
133
  }
141
- /** send a query, and wait for its response. auto-generates message ID if not provided */
134
+
142
135
  const query = async (node, timeoutMs) => {
143
136
  if (!node.attrs.id) {
144
137
  node.attrs.id = generateMessageTag()
@@ -155,7 +148,7 @@ const makeSocket = config => {
155
148
  }
156
149
  return result
157
150
  }
158
- // Validate current key-bundle on server; on failure, trigger pre-key upload and rethrow
151
+
159
152
  const digestKeyBundle = async () => {
160
153
  const res = await query({
161
154
  tag: 'iq',
@@ -168,7 +161,7 @@ const makeSocket = config => {
168
161
  throw new Error('encrypt/get digest returned no digest node')
169
162
  }
170
163
  }
171
- // Rotate our signed pre-key on server; on failure, run digest as fallback and rethrow
164
+
172
165
  const rotateSignedPreKey = async () => {
173
166
  const newId = (creds.signedPreKey.keyId || 0) + 1
174
167
  const skey = await (0, Utils_1.signedKeyPair)(creds.signedIdentityKey, newId)
@@ -183,21 +176,23 @@ const makeSocket = config => {
183
176
  }
184
177
  ]
185
178
  })
186
- // Persist new signed pre-key in creds
179
+
187
180
  ev.emit('creds.update', { signedPreKey: skey })
188
181
  }
189
182
  const executeUSyncQuery = async usyncQuery => {
190
183
  if (usyncQuery.protocols.length === 0) {
191
184
  throw new boom_1.Boom('USyncQuery must have at least one protocol')
192
185
  }
193
- // todo: validate users, throw WARNING on no valid users
194
- // variable below has only validated users
195
- const validUsers = usyncQuery.users
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
+ }
196
190
  const userNodes = validUsers.map(user => {
197
191
  return {
198
192
  tag: 'user',
199
193
  attrs: {
200
- jid: !user.phone ? user.id : undefined
194
+ jid: !user.phone ? user.id : undefined,
195
+ pn_jid: user.pnJid
201
196
  },
202
197
  content: usyncQuery.protocols.map(a => a.getUserElement(user)).filter(a => a !== null)
203
198
  }
@@ -233,8 +228,17 @@ const makeSocket = config => {
233
228
  }
234
229
  ]
235
230
  }
231
+ await (0, WAUSync_1.waitForBackoff)(usyncQuery)
236
232
  const result = await query(iq)
237
- return usyncQuery.parseUSyncQueryResult(result)
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
238
242
  }
239
243
  const onWhatsApp = async (...phoneNumber) => {
240
244
  let usyncQuery = new WAUSync_1.USyncQuery()
@@ -253,7 +257,7 @@ const makeSocket = config => {
253
257
  }
254
258
  }
255
259
  if (usyncQuery.users.length === 0) {
256
- return [] // return early without forcing an empty query
260
+ return []
257
261
  }
258
262
  const results = await executeUSyncQuery(usyncQuery)
259
263
  if (results) {
@@ -271,7 +275,7 @@ const makeSocket = config => {
271
275
  }
272
276
  }
273
277
  if (usyncQuery.users.length === 0) {
274
- return [] // return early without forcing an empty query
278
+ return []
275
279
  }
276
280
  const results = await executeUSyncQuery(usyncQuery)
277
281
  if (results) {
@@ -279,83 +283,9 @@ const makeSocket = config => {
279
283
  }
280
284
  return []
281
285
  }
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
- }
356
286
  const ev = (0, Utils_1.makeEventBuffer)(logger)
357
287
  const { creds } = authState
358
- // add transaction capability
288
+
359
289
  const keys = (0, Utils_1.addTransactionCapability)(authState.keys, logger, transactionOpts)
360
290
  const signalRepository = makeSignalRepository({ creds, keys }, logger, pnFromLIDUSync)
361
291
  let lastDateRecv
@@ -363,12 +293,11 @@ const makeSocket = config => {
363
293
  let keepAliveReq
364
294
  let qrTimer
365
295
  let closed = false
366
- const socketEndHandlers = []
367
- /** log & process any unexpected errors */
296
+
368
297
  const onUnexpectedError = (err, msg) => {
369
298
  logger.error({ err }, `unexpected error in '${msg}'`)
370
299
  }
371
- /** await the next incoming message */
300
+
372
301
  const awaitNextMessage = async sendMsg => {
373
302
  if (!ws.isOpen) {
374
303
  throw new boom_1.Boom('Connection Closed', {
@@ -393,7 +322,7 @@ const makeSocket = config => {
393
322
  }
394
323
  return result
395
324
  }
396
- /** connection handshake */
325
+
397
326
  const validateConnection = async () => {
398
327
  let helloMsg = {
399
328
  clientHello: { ephemeral: ephemeralKeyPair.public }
@@ -404,7 +333,7 @@ const makeSocket = config => {
404
333
  const result = await awaitNextMessage(init)
405
334
  const handshake = index_js_1.proto.HandshakeMessage.decode(result)
406
335
  logger.trace({ handshake }, 'handshake recv from WA')
407
- const keyEnc = noise.processHandshake(handshake, creds.noiseKey)
336
+ const { keyEnc, serverStaticPub } = noise.processHandshake(handshake, creds.noiseKey)
408
337
  let node
409
338
  if (!creds.me) {
410
339
  node = (0, Utils_1.generateRegistrationNode)(creds, config)
@@ -423,6 +352,9 @@ const makeSocket = config => {
423
352
  }).finish()
424
353
  )
425
354
  await noise.finishInit()
355
+ if (serverStaticPub && !creds.serverStaticPub) {
356
+ ev.emit('creds.update', { serverStaticPub })
357
+ }
426
358
  startKeepAliveRequest()
427
359
  }
428
360
  const getAvailablePreKeysOnServer = async () => {
@@ -439,12 +371,12 @@ const makeSocket = config => {
439
371
  const countChild = (0, WABinary_1.getBinaryNodeChild)(result, 'count')
440
372
  return +countChild.attrs.value
441
373
  }
442
- // Pre-key upload state management
374
+
443
375
  let uploadPreKeysPromise = null
444
376
  let lastUploadTime = 0
445
- /** generates and uploads a set of pre-keys to the server */
377
+
446
378
  const uploadPreKeys = async (count = Defaults_1.MIN_PREKEY_COUNT, retryCount = 0) => {
447
- // Check minimum interval (except for retries)
379
+
448
380
  if (retryCount === 0) {
449
381
  const timeSinceLastUpload = Date.now() - lastUploadTime
450
382
  if (timeSinceLastUpload < Defaults_1.MIN_UPLOAD_INTERVAL) {
@@ -452,29 +384,29 @@ const makeSocket = config => {
452
384
  return
453
385
  }
454
386
  }
455
- // Prevent multiple concurrent uploads
387
+
456
388
  if (uploadPreKeysPromise) {
457
389
  logger.debug('Pre-key upload already in progress, waiting for completion')
458
390
  await uploadPreKeysPromise
459
391
  }
460
392
  const uploadLogic = async () => {
461
393
  logger.info({ count, retryCount }, 'uploading pre-keys')
462
- // Generate and save pre-keys atomically (prevents ID collisions on retry)
394
+
463
395
  const node = await keys.transaction(async () => {
464
396
  logger.debug({ requestedCount: count }, 'generating pre-keys with requested count')
465
397
  const { update, node } = await (0, Utils_1.getNextPreKeysNode)({ creds, keys }, count)
466
- // Update credentials immediately to prevent duplicate IDs on retry
398
+
467
399
  ev.emit('creds.update', update)
468
- return node // Only return node since update is already used
400
+ return node
469
401
  }, creds?.me?.id || 'upload-pre-keys')
470
- // Upload to server (outside transaction, can fail without affecting local keys)
402
+
471
403
  try {
472
404
  await query(node)
473
405
  logger.info({ count }, 'uploaded pre-keys successfully')
474
406
  lastUploadTime = Date.now()
475
407
  } catch (uploadError) {
476
408
  logger.error({ uploadError: uploadError.toString(), count }, 'Failed to upload pre-keys to server')
477
- // Exponential backoff retry (max 3 retries)
409
+
478
410
  if (retryCount < 3) {
479
411
  const backoffDelay = Math.min(1000 * Math.pow(2, retryCount), 10000)
480
412
  logger.info(`Retrying pre-key upload in ${backoffDelay}ms`)
@@ -484,7 +416,7 @@ const makeSocket = config => {
484
416
  throw uploadError
485
417
  }
486
418
  }
487
- // Add timeout protection
419
+
488
420
  uploadPreKeysPromise = Promise.race([
489
421
  uploadLogic(),
490
422
  new Promise((_, reject) =>
@@ -532,40 +464,47 @@ const makeSocket = config => {
532
464
  }
533
465
  } catch (error) {
534
466
  logger.error({ error }, 'Failed to check/upload pre-keys during initialization')
535
- // Don't throw - allow connection to continue even if pre-key check fails
467
+
536
468
  }
537
469
  }
538
470
  const onMessageReceived = async data => {
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' })
549
- }
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')
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
+ }
566
500
  }
567
- }
568
- })
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
+ }
569
508
  }
570
509
  const end = async error => {
571
510
  if (closed) {
@@ -575,6 +514,7 @@ const makeSocket = config => {
575
514
  closed = true
576
515
  logger.info({ trace: error?.stack }, error ? 'connection errored' : 'connection closed')
577
516
  clearInterval(keepAliveReq)
517
+ clearInterval(wamFlushInterval)
578
518
  clearTimeout(qrTimer)
579
519
  ws.removeAllListeners('close')
580
520
  ws.removeAllListeners('open')
@@ -584,13 +524,6 @@ const makeSocket = config => {
584
524
  await ws.close()
585
525
  } catch {}
586
526
  }
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
- }
594
527
  ev.emit('connection.update', {
595
528
  connection: 'close',
596
529
  lastDisconnect: {
@@ -627,14 +560,13 @@ const makeSocket = config => {
627
560
  lastDateRecv = new Date()
628
561
  }
629
562
  const diff = Date.now() - lastDateRecv.getTime()
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
- */
563
+
634
564
  if (diff > keepAliveIntervalMs + 5000) {
635
565
  void end(new boom_1.Boom('Connection was lost', { statusCode: Types_1.DisconnectReason.connectionLost }))
636
566
  } else if (ws.isOpen) {
637
- // if its all good, send a keep alive request
567
+
568
+
569
+
638
570
  query({
639
571
  tag: 'iq',
640
572
  attrs: {
@@ -642,8 +574,7 @@ const makeSocket = config => {
642
574
  to: WABinary_1.S_WHATSAPP_NET,
643
575
  type: 'get',
644
576
  xmlns: 'w:p'
645
- },
646
- content: [{ tag: 'ping', attrs: {} }]
577
+ }
647
578
  }).catch(err => {
648
579
  logger.error({ trace: err.stack }, 'error in sending keep alive')
649
580
  })
@@ -651,7 +582,7 @@ const makeSocket = config => {
651
582
  logger.warn('keep alive called when WS not open')
652
583
  }
653
584
  }, keepAliveIntervalMs))
654
- /** i have no idea why this exists. pls enlighten me */
585
+
655
586
  const sendPassiveIq = tag =>
656
587
  query({
657
588
  tag: 'iq',
@@ -662,7 +593,7 @@ const makeSocket = config => {
662
593
  },
663
594
  content: [{ tag, attrs: {} }]
664
595
  })
665
- /** logout & invalidate connection */
596
+
666
597
  const logout = async msg => {
667
598
  const jid = authState.creds.me?.id
668
599
  if (jid) {
@@ -689,7 +620,7 @@ const makeSocket = config => {
689
620
  }
690
621
  const requestPairingCode = async (phoneNumber, customPairingCode) => {
691
622
  const pairingCode = customPairingCode ?? (0, Utils_1.bytesToCrockford)((0, crypto_1.randomBytes)(5))
692
- if (customPairingCode && customPairingCode?.length !== 8) {
623
+ if (customPairingCode && customPairingCode.length !== 8) {
693
624
  throw new Error('Custom pairing code must be exactly 8 chars')
694
625
  }
695
626
  authState.creds.pairingCode = pairingCode
@@ -759,7 +690,8 @@ const makeSocket = config => {
759
690
  attrs: {
760
691
  to: WABinary_1.S_WHATSAPP_NET,
761
692
  id: generateMessageTag(),
762
- xmlns: 'w:stats'
693
+ xmlns: 'w:stats',
694
+ type: 'set'
763
695
  },
764
696
  content: [
765
697
  {
@@ -770,8 +702,30 @@ const makeSocket = config => {
770
702
  ]
771
703
  })
772
704
  }
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 }
773
717
  ws.on('message', onMessageReceived)
774
718
  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
+ })
775
729
  try {
776
730
  await validateConnection()
777
731
  } catch (err) {
@@ -779,12 +733,15 @@ const makeSocket = config => {
779
733
  void end(err)
780
734
  }
781
735
  })
736
+ const wamFlushInterval = setInterval(() => {
737
+ void flushWAMEvents()
738
+ }, 30_000)
782
739
  ws.on('error', mapWebSocketError(end))
783
740
  ws.on(
784
741
  'close',
785
742
  () => void end(new boom_1.Boom('Connection Terminated', { statusCode: Types_1.DisconnectReason.connectionClosed }))
786
743
  )
787
- // the server terminated the connection
744
+
788
745
  ws.on(
789
746
  'CB:xmlstreamend',
790
747
  () =>
@@ -792,7 +749,7 @@ const makeSocket = config => {
792
749
  new boom_1.Boom('Connection Terminated by Server', { statusCode: Types_1.DisconnectReason.connectionClosed })
793
750
  )
794
751
  )
795
- // QR gen
752
+
796
753
  ws.on('CB:iq,type:set,pair-device', async stanza => {
797
754
  const iq = {
798
755
  tag: 'iq',
@@ -808,7 +765,7 @@ const makeSocket = config => {
808
765
  const noiseKeyB64 = Buffer.from(creds.noiseKey.public).toString('base64')
809
766
  const identityKeyB64 = Buffer.from(creds.signedIdentityKey.public).toString('base64')
810
767
  const advB64 = creds.advSecretKey
811
- let qrMs = qrTimeout || 60000 // time to let a QR live
768
+ let qrMs = qrTimeout || 60000
812
769
  const genPairQR = () => {
813
770
  if (!ws.isOpen) {
814
771
  return
@@ -818,16 +775,16 @@ const makeSocket = config => {
818
775
  void end(new boom_1.Boom('QR refs attempts ended', { statusCode: Types_1.DisconnectReason.timedOut }))
819
776
  return
820
777
  }
821
- const ref = refNode.content.toString('utf-8')
778
+ const ref = refNode.content.toString('utf8')
822
779
  const qr = [ref, noiseKeyB64, identityKeyB64, advB64].join(',')
823
780
  ev.emit('connection.update', { qr })
824
781
  qrTimer = setTimeout(genPairQR, qrMs)
825
- qrMs = qrTimeout || 20000 // shorter subsequent qrs
782
+ qrMs = qrTimeout || 20000
826
783
  }
827
784
  genPairQR()
828
785
  })
829
- // device paired for the first time
830
- // if device pairs successfully, the server asks to restart the connection
786
+
787
+
831
788
  ws.on('CB:iq,,pair-success', async stanza => {
832
789
  logger.debug('pair success recv')
833
790
  try {
@@ -846,16 +803,16 @@ const makeSocket = config => {
846
803
  void end(error)
847
804
  }
848
805
  })
849
- // login complete
806
+
850
807
  ws.on('CB:success', async node => {
851
808
  try {
852
809
  updateServerTimeOffset(node)
853
810
  await uploadPreKeysToServerIfRequired()
854
- // passive→active handshake only for companion (passive) sessions
811
+
855
812
  if (!config.masqueradeAsPrimary) {
856
813
  await sendPassiveIq('active')
857
814
  }
858
- // After successful login, validate our key-bundle against server
815
+
859
816
  try {
860
817
  await digestKeyBundle()
861
818
  } catch (e) {
@@ -865,7 +822,15 @@ const makeSocket = config => {
865
822
  logger.warn({ err }, 'failed to send initial passive iq')
866
823
  }
867
824
  logger.info('opened connection to WA')
868
- clearTimeout(qrTimer) // will never happen in all likelyhood -- but just in case WA sends success on first try
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()
869
834
  ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } })
870
835
  if (config.syncFullHistory && authState.creds.initialFullSyncDone !== true) {
871
836
  logger.debug('initial full history sync completed, persisting one-time flag')
@@ -878,16 +843,16 @@ const makeSocket = config => {
878
843
  process.nextTick(async () => {
879
844
  try {
880
845
  const myPN = authState.creds.me.id
881
- // Store our own LID-PN mapping
846
+
882
847
  await signalRepository.lidMapping.storeLIDPNMappings([{ lid: myLID, pn: myPN }])
883
- // Create device list for our own user (needed for bulk migration)
848
+
884
849
  const { user, device } = (0, WABinary_1.jidDecode)(myPN)
885
850
  await authState.keys.set({
886
851
  'device-list': {
887
852
  [user]: [device?.toString() || '0']
888
853
  }
889
854
  })
890
- // migrate our own session
855
+
891
856
  await signalRepository.migrateSession(myPN, myLID)
892
857
  logger.info({ myPN, myLID }, 'Own LID session created successfully')
893
858
  } catch (error) {
@@ -902,7 +867,7 @@ const makeSocket = config => {
902
867
  const { reason, statusCode } = (0, Utils_1.getErrorCodeFromStreamError)(node)
903
868
  void end(new boom_1.Boom(`Stream Errored (${reason})`, { statusCode, data: reasonNode || node }))
904
869
  })
905
- // stream fail, possible logout
870
+
906
871
  ws.on('CB:failure', node => {
907
872
  const reason = +(node.attrs.reason || 500)
908
873
  void end(new boom_1.Boom('Connection Failure', { statusCode: reason, data: node.attrs }))
@@ -912,6 +877,15 @@ const makeSocket = config => {
912
877
  new boom_1.Boom('Multi-device beta not joined', { statusCode: Types_1.DisconnectReason.multideviceMismatch })
913
878
  )
914
879
  })
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
+ })
915
889
  ws.on('CB:ib,,offline_preview', async node => {
916
890
  logger.info('offline preview received', JSON.stringify(node))
917
891
  await sendNode({
@@ -920,6 +894,19 @@ const makeSocket = config => {
920
894
  content: [{ tag: 'offline_batch', attrs: { count: '100' } }]
921
895
  })
922
896
  })
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
+ })
923
910
  ws.on('CB:ib,,edge_routing', node => {
924
911
  const edgeRoutingNode = (0, WABinary_1.getBinaryNodeChild)(node, 'edge_routing')
925
912
  const routingInfo = (0, WABinary_1.getBinaryNodeChild)(edgeRoutingNode, 'routing_info')
@@ -931,14 +918,14 @@ const makeSocket = config => {
931
918
  let didStartBuffer = false
932
919
  process.nextTick(() => {
933
920
  if (creds.me?.id) {
934
- // start buffering important events
935
- // if we're logged in
921
+
922
+
936
923
  ev.buffer()
937
924
  didStartBuffer = true
938
925
  }
939
926
  ev.emit('connection.update', { connection: 'connecting', receivedPendingNotifications: false, qr: undefined })
940
927
  })
941
- // called when all offline notifs are handled
928
+
942
929
  ws.on('CB:ib,,offline', node => {
943
930
  const child = (0, WABinary_1.getBinaryNodeChild)(node, 'offline')
944
931
  const offlineNotifs = +(child?.attrs.count || 0)
@@ -949,11 +936,14 @@ const makeSocket = config => {
949
936
  }
950
937
  ev.emit('connection.update', { receivedPendingNotifications: true })
951
938
  })
952
- // update credentials when required
939
+
953
940
  ev.on('creds.update', update => {
954
941
  const name = update.me?.name
955
- // if name has just been received
956
- if (creds.me?.name !== name) {
942
+
943
+
944
+
945
+
946
+ if (typeof name === 'string' && name.length > 0 && creds.me?.name !== name) {
957
947
  logger.debug({ name }, 'updated pushName')
958
948
  sendNode({
959
949
  tag: 'presence',
@@ -1033,9 +1023,6 @@ const makeSocket = config => {
1033
1023
  generateMessageTag
1034
1024
  )
1035
1025
  }
1036
- const registerSocketEndHandler = handler => {
1037
- socketEndHandlers.push(handler)
1038
- }
1039
1026
  return {
1040
1027
  type: 'md',
1041
1028
  ws,
@@ -1053,10 +1040,6 @@ const makeSocket = config => {
1053
1040
  sendNode,
1054
1041
  logout,
1055
1042
  end,
1056
- registerSocketEndHandler,
1057
- toPn,
1058
- toLID,
1059
- fetchUsername,
1060
1043
  onUnexpectedError,
1061
1044
  uploadPreKeys,
1062
1045
  uploadPreKeysToServerIfRequired,
@@ -1066,20 +1049,19 @@ const makeSocket = config => {
1066
1049
  updateServerTimeOffset,
1067
1050
  sendUnifiedSession,
1068
1051
  wamBuffer: publicWAMBuffer,
1069
- /** Waits for the connection to WA to reach a state */
1052
+
1070
1053
  waitForConnectionUpdate: (0, Utils_1.bindWaitForConnectionUpdate)(ev),
1071
1054
  sendWAMBuffer,
1072
1055
  executeUSyncQuery,
1073
1056
  onWhatsApp,
1074
1057
  fetchAccountReachoutTimelock,
1075
- fetchNewChatMessageCap
1058
+ fetchNewChatMessageCap,
1059
+ logger,
1060
+ masqueradeAsPrimary: config.masqueradeAsPrimary
1076
1061
  }
1077
1062
  }
1078
1063
  exports.makeSocket = makeSocket
1079
- /**
1080
- * map the websocket error to the right type
1081
- * so it can be retried by the caller
1082
- * */
1064
+
1083
1065
  function mapWebSocketError(handler) {
1084
1066
  return error => {
1085
1067
  handler(