@badzz88/baileys 8.4.9 → 8.5.0

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 (153) hide show
  1. package/WAProto/WAProto.proto +6 -347
  2. package/WAProto/fix-imports.js +85 -86
  3. package/WAProto/index.d.ts +25 -4913
  4. package/WAProto/index.js +93 -14069
  5. package/{src → lib}/Defaults/index.js +2 -13
  6. package/{src → lib}/Signal/Group/group_cipher.js +5 -5
  7. package/{src → lib}/Signal/Group/keyhelper.js +4 -4
  8. package/{src → lib}/Signal/Group/sender-chain-key.js +2 -2
  9. package/{src → lib}/Signal/Group/sender-key-distribution-message.js +1 -1
  10. package/{src → lib}/Signal/Group/sender-key-message.js +3 -3
  11. package/{src → lib}/Signal/Group/sender-key-record.js +1 -1
  12. package/{src → lib}/Signal/Group/sender-message-key.js +2 -2
  13. package/{src → lib}/Signal/libsignal.js +26 -6
  14. package/{src → lib}/Socket/chats.js +58 -616
  15. package/{src → lib}/Socket/communities.js +28 -59
  16. package/{src → lib}/Socket/graphql.js +103 -362
  17. package/{src → lib}/Socket/groups.js +29 -77
  18. package/{src → lib}/Socket/index.js +5 -3
  19. package/{src → lib}/Socket/interactive-handler.js +60 -117
  20. package/{src → lib}/Socket/interop.js +19 -36
  21. package/lib/Socket/luxu.js +387 -0
  22. package/{src → lib}/Socket/managed-account.js +27 -58
  23. package/{src → lib}/Socket/messages-recv.js +143 -715
  24. package/{src → lib}/Socket/messages-send.js +102 -412
  25. package/lib/Socket/mex.js +60 -0
  26. package/{src → lib}/Socket/newsletter.js +23 -266
  27. package/{src → lib}/Socket/privacy.js +38 -41
  28. package/{src → lib}/Socket/registration.js +106 -113
  29. package/{src → lib}/Socket/socket.js +101 -88
  30. package/{src → lib}/Socket/text-router.js +17 -1
  31. package/lib/Socket/username.js +243 -0
  32. package/lib/Store/keyed-db.js +108 -0
  33. package/{src → lib}/Store/make-in-memory-store.js +104 -6
  34. package/lib/Types/Mex.js +112 -0
  35. package/{src → lib}/Types/Newsletter.js +41 -72
  36. package/{src → lib}/Types/index.js +1 -0
  37. package/{src → lib}/Utils/chat-utils.js +9 -73
  38. package/lib/Utils/command-loader.d.ts +31 -0
  39. package/lib/Utils/command-loader.js +100 -0
  40. package/lib/Utils/companion-reg-client-utils.js +42 -0
  41. package/lib/Utils/crypto.js +155 -0
  42. package/lib/Utils/curve25519-js.js +275 -0
  43. package/{src → lib}/Utils/decode-wa-message.js +36 -284
  44. package/{src → lib}/Utils/event-buffer.js +27 -6
  45. package/{src → lib}/Utils/generics.js +37 -114
  46. package/{src → lib}/Utils/index.js +5 -1
  47. package/lib/Utils/lt-hash.js +39 -0
  48. package/{src → lib}/Utils/message-retry-manager.js +39 -0
  49. package/{src → lib}/Utils/messages-media.js +58 -105
  50. package/{src → lib}/Utils/messages.js +529 -115
  51. package/lib/Utils/meta-ai-msmsg.js +262 -0
  52. package/lib/Utils/native-bridge.js +82 -0
  53. package/{src → lib}/Utils/noise-handler.js +15 -13
  54. package/{src → lib}/Utils/process-message.js +55 -54
  55. package/{src → lib}/Utils/reporting-utils.js +2 -2
  56. package/lib/Utils/session-pool.d.ts +16 -0
  57. package/lib/Utils/session-pool.js +94 -0
  58. package/{src → lib}/Utils/signal.js +51 -19
  59. package/{src → lib}/Utils/stanza-ack.js +2 -36
  60. package/lib/Utils/sticker.d.ts +17 -0
  61. package/lib/Utils/sticker.js +145 -0
  62. package/{src → lib}/Utils/tc-token-utils.js +1 -1
  63. package/{src → lib}/Utils/validate-connection.js +11 -40
  64. package/lib/Utils/view-once-cache.d.ts +9 -0
  65. package/lib/Utils/view-once-cache.js +79 -0
  66. package/{src → lib}/WABinary/decode.js +4 -38
  67. package/lib/WABinary/encode.js +228 -0
  68. package/{src → lib}/WABinary/generic-utils.js +93 -1
  69. package/{src → lib}/WABinary/jid-utils.js +3 -16
  70. package/{src → lib}/WAM/encode.js +1 -1
  71. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +43 -0
  72. package/{src → lib}/WAUSync/Protocols/USyncContactProtocol.js +1 -7
  73. package/{src → lib}/WAUSync/Protocols/USyncDeviceProtocol.js +5 -13
  74. package/{src → lib}/WAUSync/Protocols/USyncDisappearingModeProtocol.js +4 -1
  75. package/{src → lib}/WAUSync/Protocols/USyncFeatureProtocol.js +2 -17
  76. package/{src → lib}/WAUSync/Protocols/USyncStatusProtocol.js +2 -7
  77. package/{src → lib}/WAUSync/Protocols/USyncUsernameProtocol.js +1 -2
  78. package/{src → lib}/WAUSync/Protocols/index.js +0 -1
  79. package/{src → lib}/WAUSync/USyncQuery.js +6 -77
  80. package/{src → lib}/WAUSync/USyncUser.js +8 -16
  81. package/{src → lib}/WAUSync/index.js +0 -1
  82. package/{src → lib}/antiban.js +266 -355
  83. package/{src → lib}/index.js +2 -2
  84. package/package.json +18 -15
  85. package/WAProto/fix-import.js +0 -38
  86. package/src/Socket/mex.js +0 -47
  87. package/src/Socket/username.js +0 -234
  88. package/src/Utils/crypto.js +0 -125
  89. package/src/Utils/lt-hash.js +0 -10
  90. package/src/Utils/message-inspect.js +0 -400
  91. package/src/Utils/meta-ai-msmsg.js +0 -133
  92. package/src/WABinary/encode.js +0 -58
  93. package/src/WAUSync/Protocols/USyncBusinessProtocol.js +0 -100
  94. package/src/WAUSync/USyncBackoff.js +0 -31
  95. /package/{src → lib}/Defaults/phonenumber-mcc.json +0 -0
  96. /package/{src → lib}/Signal/Group/ciphertext-message.js +0 -0
  97. /package/{src → lib}/Signal/Group/group-session-builder.js +0 -0
  98. /package/{src → lib}/Signal/Group/index.js +0 -0
  99. /package/{src → lib}/Signal/Group/sender-key-name.js +0 -0
  100. /package/{src → lib}/Signal/Group/sender-key-state.js +0 -0
  101. /package/{src → lib}/Signal/lid-mapping.js +0 -0
  102. /package/{src → lib}/Socket/Client/index.js +0 -0
  103. /package/{src → lib}/Socket/Client/types.js +0 -0
  104. /package/{src → lib}/Socket/Client/websocket.js +0 -0
  105. /package/{src → lib}/Socket/aigroups.js +0 -0
  106. /package/{src → lib}/Socket/business.js +0 -0
  107. /package/{src → lib}/Store/index.js +0 -0
  108. /package/{src → lib}/Store/make-cache-manager-store.js +0 -0
  109. /package/{src → lib}/Store/make-ordered-dictionary.js +0 -0
  110. /package/{src → lib}/Store/object-repository.js +0 -0
  111. /package/{src → lib}/Types/Auth.js +0 -0
  112. /package/{src → lib}/Types/Bussines.js +0 -0
  113. /package/{src → lib}/Types/Call.js +0 -0
  114. /package/{src → lib}/Types/Chat.js +0 -0
  115. /package/{src → lib}/Types/Contact.js +0 -0
  116. /package/{src → lib}/Types/Events.js +0 -0
  117. /package/{src → lib}/Types/GroupMetadata.js +0 -0
  118. /package/{src → lib}/Types/Label.js +0 -0
  119. /package/{src → lib}/Types/LabelAssociation.js +0 -0
  120. /package/{src → lib}/Types/Message.js +0 -0
  121. /package/{src → lib}/Types/Product.js +0 -0
  122. /package/{src → lib}/Types/Signal.js +0 -0
  123. /package/{src → lib}/Types/Socket.js +0 -0
  124. /package/{src → lib}/Types/State.js +0 -0
  125. /package/{src → lib}/Types/USync.js +0 -0
  126. /package/{src → lib}/Utils/auth-utils.js +0 -0
  127. /package/{src → lib}/Utils/browser-utils.js +0 -0
  128. /package/{src → lib}/Utils/business.js +0 -0
  129. /package/{src → lib}/Utils/consumer-application.js +0 -0
  130. /package/{src → lib}/Utils/group-history.js +0 -0
  131. /package/{src → lib}/Utils/history.js +0 -0
  132. /package/{src → lib}/Utils/identity-change-handler.js +0 -0
  133. /package/{src → lib}/Utils/jid-display-normalization.js +0 -0
  134. /package/{src → lib}/Utils/link-preview.js +0 -0
  135. /package/{src → lib}/Utils/logger.js +0 -0
  136. /package/{src → lib}/Utils/make-mutex.js +0 -0
  137. /package/{src → lib}/Utils/message-composer.js +0 -0
  138. /package/{src → lib}/Utils/offline-node-processor.js +0 -0
  139. /package/{src → lib}/Utils/pre-key-manager.js +0 -0
  140. /package/{src → lib}/Utils/sync-action-utils.js +0 -0
  141. /package/{src → lib}/Utils/use-multi-file-auth-state.js +0 -0
  142. /package/{src → lib}/Utils/voip-rekey.js +0 -0
  143. /package/{src → lib}/WABinary/constants.js +0 -0
  144. /package/{src → lib}/WABinary/index.js +0 -0
  145. /package/{src → lib}/WABinary/types.js +0 -0
  146. /package/{src → lib}/WAM/BinaryInfo.js +0 -0
  147. /package/{src → lib}/WAM/constants.js +0 -0
  148. /package/{src → lib}/WAM/index.js +0 -0
  149. /package/{src → lib}/WAUSync/Protocols/USyncBotProfileProtocol.js +0 -0
  150. /package/{src → lib}/WAUSync/Protocols/USyncLIDProtocol.js +0 -0
  151. /package/{src → lib}/WAUSync/Protocols/USyncPictureProtocol.js +0 -0
  152. /package/{src → lib}/WAUSync/Protocols/USyncSidelistProtocol.js +0 -0
  153. /package/{src → lib}/WAUSync/Protocols/USyncTextStatusProtocol.js +0 -0
@@ -0,0 +1,262 @@
1
+ 'use strict'
2
+
3
+ const Crypto_1 = require('./crypto')
4
+ const { proto } = require('../../WAProto')
5
+
6
+ const BOT_MESSAGE_INFO = 'Bot Message'
7
+ const KEY_LENGTH = 32
8
+ const AUTH_TAG_LENGTH = 16
9
+
10
+ const MSG_ID_HEX_RE = /^[0-9A-Fa-f]{32}$/
11
+
12
+ const unpadRandomMax16 = value => {
13
+ const bytes = new Uint8Array(value)
14
+ if (!bytes.length) {
15
+ throw new Error('unpadPkcs7 given empty bytes')
16
+ }
17
+
18
+ const padLength = bytes[bytes.length - 1]
19
+ if (padLength > bytes.length) {
20
+ throw new Error(`unpad given ${bytes.length} bytes, but pad is ${padLength}`)
21
+ }
22
+
23
+ return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.length - padLength)
24
+ }
25
+
26
+ const toBuffer = value => {
27
+ if (Buffer.isBuffer(value)) {
28
+ return value
29
+ }
30
+
31
+ if (value instanceof Uint8Array) {
32
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength)
33
+ }
34
+
35
+ return Buffer.from(value)
36
+ }
37
+
38
+ const normalizeLidJid = jid => {
39
+ if (!jid || !jid.endsWith('@lid') || !jid.includes(':')) {
40
+ return jid
41
+ }
42
+
43
+ return `${jid.split(':')[0]}@lid`
44
+ }
45
+
46
+ const buildMessageIdRepresentations = messageId => {
47
+ const ascii = Buffer.from(messageId)
48
+ const binary = MSG_ID_HEX_RE.test(messageId) ? Buffer.from(messageId, 'hex') : ascii
49
+ return [
50
+ { label: 'msgIdAscii', value: ascii },
51
+ ...(binary.equals(ascii) ? [] : [{ label: 'msgIdBinary', value: binary }])
52
+ ]
53
+ }
54
+
55
+ const pushUnique = (items, seen, item) => {
56
+ const key = JSON.stringify([
57
+ item.messageId,
58
+ item.idSource,
59
+ item.idSources,
60
+ item.infoSource,
61
+ item.aadSource,
62
+ item.info.toString('hex'),
63
+ item.aad.toString('hex')
64
+ ])
65
+
66
+ if (!seen.has(key)) {
67
+ seen.add(key)
68
+ items.push(item)
69
+ }
70
+ }
71
+
72
+ const getCandidateIds = messageKey => {
73
+ const orderedCandidates = [
74
+ messageKey?.botType === 'full'
75
+ ? { source: 'stanzaId', messageId: messageKey?.stanzaId }
76
+ : { source: 'botEditTargetId', messageId: messageKey?.botEditTargetId },
77
+ { source: 'targetId', messageId: messageKey?.targetId },
78
+ { source: 'metaTargetId', messageId: messageKey?.metaTargetId },
79
+ { source: 'stanzaId', messageId: messageKey?.stanzaId }
80
+ ]
81
+
82
+ const targetIdCandidates = Array.isArray(messageKey?.targetIdCandidates) ? messageKey.targetIdCandidates : []
83
+ for (let index = 0; index < targetIdCandidates.length; index += 1) {
84
+ orderedCandidates.push({
85
+ source: `targetIdCandidates[${index}]`,
86
+ messageId: targetIdCandidates[index]
87
+ })
88
+ }
89
+
90
+ const grouped = new Map()
91
+ for (const candidate of orderedCandidates) {
92
+ if (!candidate.messageId) {
93
+ continue
94
+ }
95
+
96
+ const messageId = String(candidate.messageId)
97
+ const existing = grouped.get(messageId)
98
+ if (existing) {
99
+ if (!existing.idSources.includes(candidate.source)) {
100
+ existing.idSources.push(candidate.source)
101
+ }
102
+ } else {
103
+ grouped.set(messageId, {
104
+ messageId,
105
+ idSource: candidate.source,
106
+ idSources: [candidate.source]
107
+ })
108
+ }
109
+ }
110
+
111
+ return Array.from(grouped.values())
112
+ }
113
+
114
+ const getJidCandidates = messageKey => {
115
+ const ordered = [
116
+ { source: 'meId', jid: messageKey?.meId },
117
+ { source: 'conversationJid', jid: messageKey?.conversationJid },
118
+ { source: 'senderJid', jid: messageKey?.senderJid },
119
+ { source: 'meLidNormalized', jid: normalizeLidJid(messageKey?.meLid) }
120
+ ]
121
+
122
+ const seen = new Set()
123
+ const candidates = []
124
+ for (const candidate of ordered) {
125
+ if (!candidate.jid) {
126
+ continue
127
+ }
128
+
129
+ const jid = String(candidate.jid)
130
+ if (!seen.has(jid)) {
131
+ seen.add(jid)
132
+ candidates.push({ source: candidate.source, jid, value: Buffer.from(jid) })
133
+ }
134
+ }
135
+
136
+ return candidates
137
+ }
138
+
139
+ const buildMsmsgDecryptionStrategies = messageKey => {
140
+ const botJid = String(messageKey?.participant || '')
141
+ const botJidBuffer = Buffer.from(botJid)
142
+ const targetIds = getCandidateIds(messageKey)
143
+ const jidCandidates = getJidCandidates(messageKey)
144
+ const primaryJid = jidCandidates[0]
145
+ const alternateJid = jidCandidates.find(
146
+ candidate => candidate.source !== primaryJid?.source && candidate.jid !== botJid
147
+ )
148
+ const strategies = []
149
+ const seen = new Set()
150
+
151
+ for (const idCandidate of targetIds) {
152
+ const idForms = buildMessageIdRepresentations(idCandidate.messageId)
153
+ for (const idForm of idForms) {
154
+ pushUnique(strategies, seen, {
155
+ mode: '2step',
156
+ idSource: idCandidate.idSource,
157
+ idSources: idCandidate.idSources,
158
+ infoSource: `${idForm.label}+meId+botJid`,
159
+ aadSource: `${idForm.label}+0+botJid`,
160
+ authTagLayout: 'trailing',
161
+ messageId: idCandidate.messageId,
162
+ info: Buffer.concat([idForm.value, primaryJid.value, botJidBuffer, Buffer.alloc(0)]),
163
+ aad: Buffer.concat([idForm.value, Buffer.from([0]), botJidBuffer]),
164
+ attemptLabel: `${idCandidate.idSource}:${idForm.label}:primary`
165
+ })
166
+
167
+ if (alternateJid) {
168
+ pushUnique(strategies, seen, {
169
+ mode: '2step',
170
+ idSource: idCandidate.idSource,
171
+ idSources: idCandidate.idSources,
172
+ infoSource: `${idForm.label}+${alternateJid.source}+botJid`,
173
+ aadSource: `${idForm.label}+0+${alternateJid.source}`,
174
+ authTagLayout: 'trailing',
175
+ messageId: idCandidate.messageId,
176
+ info: Buffer.concat([idForm.value, alternateJid.value, botJidBuffer, Buffer.alloc(0)]),
177
+ aad: Buffer.concat([idForm.value, Buffer.from([0]), alternateJid.value]),
178
+ attemptLabel: `${idCandidate.idSource}:${idForm.label}:${alternateJid.source}`
179
+ })
180
+ }
181
+ }
182
+ }
183
+
184
+ return strategies.slice(0, 12)
185
+ }
186
+
187
+ const assertRequired = (value, label) => {
188
+ if (
189
+ !value ||
190
+ (Buffer.isBuffer(value) && value.length === 0) ||
191
+ (value instanceof Uint8Array && value.byteLength === 0)
192
+ ) {
193
+ throw new Error(`Missing required ${label} for msmsg decryption`)
194
+ }
195
+ }
196
+
197
+ const decryptWithStrategy = (messageSecret, msMsg, strategy) => {
198
+ const baseSecret = Buffer.from(Crypto_1.hkdf(toBuffer(messageSecret), KEY_LENGTH, { info: BOT_MESSAGE_INFO }))
199
+ const key = Buffer.from(Crypto_1.hkdf(baseSecret, KEY_LENGTH, { info: strategy.info }))
200
+ const payload = toBuffer(msMsg.encPayload)
201
+ // ciphertext = payload without last 16 bytes (auth tag) + auth tag appended → standard GCM layout
202
+ const ciphertextWithTag = Buffer.concat([payload.slice(0, -AUTH_TAG_LENGTH), payload.slice(-AUTH_TAG_LENGTH)])
203
+ return Buffer.from(Crypto_1.aesDecryptGCM(ciphertextWithTag, key, toBuffer(msMsg.encIv), strategy.aad))
204
+ }
205
+
206
+ const decodeDecryptedMsmsgMessage = decrypted => {
207
+ const messageBuffer = toBuffer(decrypted)
208
+
209
+ try {
210
+ const unpadded = Buffer.from(unpadRandomMax16(messageBuffer))
211
+ const decoded = proto.Message.decode(unpadded)
212
+ const hasContent = Object.keys(decoded).some(key => key !== 'messageContextInfo' && decoded[key] != null)
213
+ if (hasContent) {
214
+ return decoded
215
+ }
216
+ } catch {}
217
+
218
+ return proto.Message.decode(messageBuffer)
219
+ }
220
+
221
+ const decryptMsmsgBotMessage = async (messageSecret, messageKey, msMsg) => {
222
+ assertRequired(messageSecret, 'messageSecret')
223
+ assertRequired(messageKey?.participant, 'participant')
224
+ assertRequired(messageKey?.meId, 'meId')
225
+ assertRequired(msMsg?.encIv, 'encIv')
226
+ assertRequired(msMsg?.encPayload, 'encPayload')
227
+ if (getCandidateIds(messageKey).length === 0) {
228
+ throw new Error('Missing required target message id for msmsg decryption')
229
+ }
230
+
231
+ const strategies = buildMsmsgDecryptionStrategies(messageKey)
232
+ const attemptedStrategies = []
233
+ let lastError
234
+
235
+ for (const strategy of strategies) {
236
+ const attempt = {
237
+ idSource: strategy.idSource,
238
+ idSources: strategy.idSources,
239
+ infoSource: strategy.infoSource,
240
+ aadSource: strategy.aadSource,
241
+ messageId: strategy.messageId
242
+ }
243
+
244
+ try {
245
+ return await decryptWithStrategy(messageSecret, msMsg, strategy)
246
+ } catch (error) {
247
+ attemptedStrategies.push(attempt)
248
+ lastError = error
249
+ }
250
+ }
251
+
252
+ const error = new Error('Failed to decrypt msmsg with bounded deterministic strategies')
253
+ error.attemptedStrategies = attemptedStrategies
254
+ error.cause = lastError
255
+ throw error
256
+ }
257
+
258
+ module.exports = {
259
+ buildMsmsgDecryptionStrategies,
260
+ decodeDecryptedMsmsgMessage,
261
+ decryptMsmsgBotMessage
262
+ }
@@ -0,0 +1,82 @@
1
+ 'use strict'
2
+ Object.defineProperty(exports, '__esModule', { value: true })
3
+ exports.loadRustBridge = void 0
4
+ const fs = require('fs')
5
+ const path = require('path')
6
+
7
+ const BRIDGE_NAME = 'whatsapp-rust-bridge'
8
+ let cached
9
+ let attempted = false
10
+
11
+ /**
12
+ * Finds the installed package directory for `pkgName` by walking the same
13
+ * node_modules search path Node itself would use, without going through
14
+ * Node's package "exports" resolution gate.
15
+ */
16
+ const findPackageDir = pkgName => {
17
+ let searchPaths
18
+ try {
19
+ searchPaths = require.resolve.paths(pkgName) || []
20
+ } catch {
21
+ searchPaths = []
22
+ }
23
+ for (const dir of searchPaths) {
24
+ const candidate = path.join(dir, pkgName)
25
+ if (fs.existsSync(path.join(candidate, 'package.json'))) {
26
+ return candidate
27
+ }
28
+ }
29
+ return null
30
+ }
31
+
32
+ /**
33
+ * Loads whatsapp-rust-bridge, tolerating published versions/forks whose
34
+ * package.json ships an "exports" map with no "." entry (or an otherwise
35
+ * broken one) -- which makes a plain `require('whatsapp-rust-bridge')` throw
36
+ * ERR_PACKAGE_PATH_NOT_EXPORTED even though the module's files are present
37
+ * and perfectly usable. In that case, we locate the package directory
38
+ * ourselves and require its real entry file directly, bypassing the broken
39
+ * "exports" gate. Returns the loaded module, or null if it's unavailable.
40
+ */
41
+ const loadRustBridge = () => {
42
+ if (attempted) return cached
43
+ attempted = true
44
+ try {
45
+ cached = require(BRIDGE_NAME)
46
+ return cached
47
+ } catch (error) {
48
+ const recoverableCodes = new Set(['ERR_PACKAGE_PATH_NOT_EXPORTED', 'ERR_PACKAGE_IMPORT_NOT_DEFINED'])
49
+ if (!recoverableCodes.has(error?.code)) {
50
+ cached = null
51
+ return null
52
+ }
53
+ try {
54
+ const dir = findPackageDir(BRIDGE_NAME)
55
+ if (!dir) {
56
+ cached = null
57
+ return null
58
+ }
59
+ const pkgJson = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'))
60
+ const candidates = [
61
+ pkgJson.main,
62
+ 'index.js',
63
+ 'index.node',
64
+ 'dist/index.js',
65
+ 'lib/index.js',
66
+ 'build/index.js'
67
+ ].filter(Boolean)
68
+ for (const rel of candidates) {
69
+ const fullPath = path.join(dir, rel)
70
+ if (fs.existsSync(fullPath)) {
71
+ cached = require(fullPath)
72
+ return cached
73
+ }
74
+ }
75
+ } catch {
76
+ // fall through to returning null below
77
+ }
78
+ cached = null
79
+ return null
80
+ }
81
+ }
82
+ exports.loadRustBridge = loadRustBridge
@@ -4,7 +4,7 @@ exports.makeNoiseHandler = void 0
4
4
  const boom_1 = require('@hapi/boom')
5
5
  const index_js_1 = require('../../WAProto/index.js')
6
6
  const Defaults_1 = require('../Defaults')
7
- const rb = require('whatsapp-rust-bridge-baron')
7
+ const rb = require('./native-bridge').loadRustBridge()
8
8
 
9
9
  // rustNodeToJs mirrors the one in decode.js — converts InternalBinaryNode to plain JS object
10
10
  const rustNodeToJs = node => {
@@ -33,6 +33,14 @@ const makeNoiseHandler = ({
33
33
  }) => {
34
34
  logger = logger.child({ class: 'ns' })
35
35
 
36
+ if (!rb) {
37
+ throw new Error(
38
+ 'Establishing a WhatsApp connection requires the "whatsapp-rust-bridge" native module ' +
39
+ '(Noise protocol handshake + session), which failed to load. Make sure it is installed: ' +
40
+ 'npm install whatsapp-rust-bridge@0.5.4'
41
+ )
42
+ }
43
+
36
44
  const session = new rb.NoiseSession(
37
45
  publicKey instanceof Uint8Array ? publicKey : new Uint8Array(publicKey),
38
46
  NOISE_HEADER instanceof Uint8Array ? NOISE_HEADER : new Uint8Array(NOISE_HEADER),
@@ -85,7 +93,7 @@ const makeNoiseHandler = ({
85
93
  } else {
86
94
  try {
87
95
  frame = rustNodeToJs(item.toJSON())
88
- } catch {
96
+ } catch (e) {
89
97
  continue
90
98
  }
91
99
  }
@@ -140,20 +148,13 @@ const makeNoiseHandler = ({
140
148
  throw new boom_1.Boom('certification match failed', { statusCode: 400 })
141
149
  }
142
150
 
143
- // Extract server static key from leaf cert — used to cache for NoiseIkSession (fast reconnect)
144
- let serverStaticPub
145
- try {
146
- const leafDetails = index_js_1.proto.CertChain.NoiseCertificate.Details.decode(leaf.details)
147
- if (leafDetails.key?.length === 32) serverStaticPub = Buffer.from(leafDetails.key)
148
- } catch {}
149
-
150
151
  // Encrypt our static key and do final ECDH
151
152
  const keyEnc = session.processHandshakeFinish(
152
153
  noiseKey.public instanceof Uint8Array ? noiseKey.public : new Uint8Array(noiseKey.public),
153
154
  noiseKey.private instanceof Uint8Array ? noiseKey.private : new Uint8Array(noiseKey.private),
154
155
  serverHello.ephemeral
155
156
  )
156
- return { keyEnc: Buffer.from(keyEnc), serverStaticPub }
157
+ return Buffer.from(keyEnc)
157
158
  },
158
159
  encodeFrame: data => {
159
160
  const u8 = data instanceof Uint8Array ? data : new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
@@ -161,13 +162,14 @@ const makeNoiseHandler = ({
161
162
  },
162
163
  decodeFrame: async (newData, onFrame) => {
163
164
  if (isWaitingForTransport) {
164
- pendingBytes = pendingBytes ? Buffer.concat([pendingBytes, Buffer.from(newData)]) : Buffer.from(newData)
165
+ pendingBytes = pendingBytes
166
+ ? Buffer.concat([pendingBytes, Buffer.from(newData)])
167
+ : Buffer.from(newData)
165
168
  pendingOnFrame = onFrame
166
169
  return
167
170
  }
168
171
 
169
- const u8 =
170
- newData instanceof Uint8Array ? newData : new Uint8Array(newData.buffer, newData.byteOffset, newData.byteLength)
172
+ const u8 = newData instanceof Uint8Array ? newData : new Uint8Array(newData.buffer, newData.byteOffset, newData.byteLength)
171
173
  const frames = session.decodeFrame(u8)
172
174
 
173
175
  for (let i = 0; i < frames.length; i++) {
@@ -110,17 +110,14 @@ const cleanMessage = (message, meId, meLid) => {
110
110
  }
111
111
  }
112
112
  exports.cleanMessage = cleanMessage
113
+ // TODO: target:audit AUDIT THIS FUNCTION AGAIN
113
114
  const isRealMessage = message => {
114
- // Stub-type system events (missed calls, participant-add) never carry message.message,
115
- // so they must short-circuit here — gating them behind hasSomeContent below made this
116
- // branch permanently dead (audited 2026-07-19).
117
- if (REAL_MSG_STUB_TYPES.has(message.messageStubType) || REAL_MSG_REQ_ME_STUB_TYPES.has(message.messageStubType)) {
118
- return true
119
- }
120
115
  const normalizedContent = (0, messages_1.normalizeMessageContent)(message.message)
121
116
  const hasSomeContent = !!(0, messages_1.getContentType)(normalizedContent)
122
117
  return (
123
- !!normalizedContent &&
118
+ (!!normalizedContent ||
119
+ REAL_MSG_STUB_TYPES.has(message.messageStubType) ||
120
+ REAL_MSG_REQ_ME_STUB_TYPES.has(message.messageStubType)) &&
124
121
  hasSomeContent &&
125
122
  !normalizedContent?.protocolMessage &&
126
123
  !normalizedContent?.reactionMessage &&
@@ -682,7 +679,7 @@ const processMessage = async (
682
679
  {
683
680
  id: jid,
684
681
  ...update,
685
- author: message.key.participant,
682
+ author: message.key.participant ?? undefined,
686
683
  authorPn: message.key.participantAlt,
687
684
  authorUsername: message.key.participantUsername
688
685
  }
@@ -763,7 +760,7 @@ const processMessage = async (
763
760
  const approvalMode = message.messageStubParameters?.[0]
764
761
  emitGroupUpdate({ joinApprovalMode: approvalMode === 'on' })
765
762
  break
766
- case Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD:
763
+ case Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD: // TODO: Add other events
767
764
  const participant = JSON.parse(message.messageStubParameters?.[0])
768
765
  const action = message.messageStubParameters?.[1]
769
766
  const method = message.messageStubParameters?.[2]
@@ -995,51 +992,55 @@ const processMessage = async (
995
992
  emitGroupUpdate({ hidden: true })
996
993
  break
997
994
  }
998
- } else if (content?.pollUpdateMessage) {
999
- const creationMsgKey = content.pollUpdateMessage.pollCreationMessageKey
1000
- const pollMsg = await getMessage(creationMsgKey)
1001
- if (pollMsg) {
1002
- // message may be wrapped in viewOnce or botInvoke — unwrap to find messageSecret
1003
- const inner = pollMsg.viewOnceMessage?.message || pollMsg.botInvokeMessage?.message || pollMsg
1004
- const pollEncKey = inner.messageContextInfo?.messageSecret || pollMsg.messageContextInfo?.messageSecret || null
1005
- if (!pollEncKey) {
1006
- logger?.warn({ creationMsgKey }, 'poll: messageSecret missing, cannot decrypt vote')
1007
- } else {
1008
- // prefer LID for pollCreatorJid; WA uses the addressing JID of the poll creator
1009
- const rawLid = creds.me?.lid
1010
- const meLidNorm = rawLid ? `${rawLid.split(':')[0]}@lid` : ''
1011
- const pollCreatorJid = meLidNorm || (0, WABinary_1.jidNormalizedUser)(meId)
1012
- // voterJid: use the primary addressing JID (LID or PN) from the update key
1013
- const voterJid = message.key.fromMe ? pollCreatorJid : message.key.participant || message.key.remoteJid
1014
- try {
1015
- const voteMsg = decryptPollVote(content.pollUpdateMessage.vote, {
1016
- pollEncKey,
1017
- pollCreatorJid,
1018
- pollMsgId: creationMsgKey.id,
1019
- voterJid
1020
- })
1021
- ev.emit('messages.update', [
1022
- {
1023
- key: creationMsgKey,
1024
- update: {
1025
- pollUpdates: [
1026
- {
1027
- pollUpdateMessageKey: message.key,
1028
- vote: voteMsg,
1029
- senderTimestampMs: (0, generics_1.toNumber)(content.pollUpdateMessage.senderTimestampMs)
1030
- }
1031
- ]
1032
- }
1033
- }
1034
- ])
1035
- } catch (err) {
1036
- logger?.warn({ err, creationMsgKey, pollCreatorJid, voterJid }, 'failed to decrypt poll vote')
1037
- }
1038
- }
1039
- } else {
1040
- logger?.warn({ creationMsgKey }, 'poll: creation message not found, cannot decrypt vote')
1041
- }
1042
- }
995
+ } /* else if(content?.pollUpdateMessage) {
996
+ const creationMsgKey = content.pollUpdateMessage.pollCreationMessageKey!
997
+ // we need to fetch the poll creation message to get the poll enc key
998
+ // TODO: make standalone, remove getMessage reference
999
+ // TODO: Remove entirely
1000
+ const pollMsg = await getMessage(creationMsgKey)
1001
+ if(pollMsg) {
1002
+ const meIdNormalised = jidNormalizedUser(meId)
1003
+ const pollCreatorJid = getKeyAuthor(creationMsgKey, meIdNormalised)
1004
+ const voterJid = getKeyAuthor(message.key, meIdNormalised)
1005
+ const pollEncKey = pollMsg.messageContextInfo?.messageSecret!
1006
+
1007
+ try {
1008
+ const voteMsg = decryptPollVote(
1009
+ content.pollUpdateMessage.vote!,
1010
+ {
1011
+ pollEncKey,
1012
+ pollCreatorJid,
1013
+ pollMsgId: creationMsgKey.id!,
1014
+ voterJid,
1015
+ }
1016
+ )
1017
+ ev.emit('messages.update', [
1018
+ {
1019
+ key: creationMsgKey,
1020
+ update: {
1021
+ pollUpdates: [
1022
+ {
1023
+ pollUpdateMessageKey: message.key,
1024
+ vote: voteMsg,
1025
+ senderTimestampMs: (content.pollUpdateMessage.senderTimestampMs! as Long).toNumber(),
1026
+ }
1027
+ ]
1028
+ }
1029
+ }
1030
+ ])
1031
+ } catch(err) {
1032
+ logger?.warn(
1033
+ { err, creationMsgKey },
1034
+ 'failed to decrypt poll vote'
1035
+ )
1036
+ }
1037
+ } else {
1038
+ logger?.warn(
1039
+ { creationMsgKey },
1040
+ 'poll creation message not found, cannot decrypt update'
1041
+ )
1042
+ }
1043
+ } */
1043
1044
  if (Object.keys(chat).length > 1) {
1044
1045
  ev.emit('chats.update', [chat])
1045
1046
  }
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
  Object.defineProperty(exports, '__esModule', { value: true })
3
3
  exports.getMessageReportingToken = exports.shouldIncludeReportingToken = void 0
4
- const rb = require('whatsapp-rust-bridge-baron')
4
+ const Crypto_1 = require('./crypto')
5
5
  const crypto_2 = require('./crypto')
6
6
  const reportingFields = [
7
7
  { f: 1 },
@@ -246,7 +246,7 @@ const getMessageReportingToken = async (msgProtobuf, message, key) => {
246
246
  if (!content || content.length === 0) {
247
247
  return null
248
248
  }
249
- const reportingToken = Buffer.from(rb.hmacSign(content, reportingSecret)).subarray(0, 16)
249
+ const reportingToken = Buffer.from(Crypto_1.hmacSign(content, reportingSecret)).subarray(0, 16)
250
250
  return {
251
251
  tag: 'reporting',
252
252
  attrs: {},
@@ -0,0 +1,16 @@
1
+ export interface SessionPoolOptions {
2
+ makeSocket: (sessionId: string) => Promise<any> | any
3
+ logger?: any
4
+ maxBackoffMs?: number
5
+ onSessionUpdate?: (sessionId: string, sock: any) => void
6
+ onLoggedOut?: (sessionId: string) => void
7
+ }
8
+
9
+ export interface SessionPool {
10
+ add: (sessionId: string) => Promise<void>
11
+ remove: (sessionId: string) => void
12
+ get: (sessionId: string) => any
13
+ list: () => string[]
14
+ }
15
+
16
+ export function createSessionPool(options: SessionPoolOptions): SessionPool
@@ -0,0 +1,94 @@
1
+ 'use strict'
2
+ Object.defineProperty(exports, '__esModule', { value: true })
3
+ exports.createSessionPool = void 0
4
+
5
+ const DISCONNECT_REASON_LOGGED_OUT = 401
6
+
7
+ /**
8
+ * Manages multiple Baileys socket instances (one per account/session), automatically
9
+ * restarting any session that disconnects -- except when it was logged out -- using
10
+ * exponential backoff with jitter, instead of every project re-implementing its own
11
+ * reconnect loop.
12
+ *
13
+ * @param {{
14
+ * makeSocket: (sessionId: string) => Promise<any> | any,
15
+ * logger?: any,
16
+ * maxBackoffMs?: number,
17
+ * onSessionUpdate?: (sessionId: string, sock: any) => void,
18
+ * onLoggedOut?: (sessionId: string) => void
19
+ * }} options
20
+ * @returns {{
21
+ * add: (sessionId: string) => Promise<void>,
22
+ * remove: (sessionId: string) => void,
23
+ * get: (sessionId: string) => any,
24
+ * list: () => string[]
25
+ * }}
26
+ */
27
+ const createSessionPool = options => {
28
+ const logger = options.logger
29
+ const maxBackoffMs = options.maxBackoffMs ?? 60000
30
+ const sessions = new Map() // sessionId -> { sock, attempts, stopped }
31
+
32
+ const computeBackoffMs = attempts => {
33
+ const base = Math.min(1000 * 2 ** attempts, maxBackoffMs)
34
+ return base + Math.floor(Math.random() * (base * 0.2))
35
+ }
36
+
37
+ const start = async sessionId => {
38
+ const entry = sessions.get(sessionId) || { attempts: 0, stopped: false }
39
+ sessions.set(sessionId, entry)
40
+ if (entry.stopped) return
41
+ try {
42
+ const sock = await options.makeSocket(sessionId)
43
+ entry.sock = sock
44
+ options.onSessionUpdate?.(sessionId, sock)
45
+ sock.ev.on('connection.update', update => {
46
+ if (update.connection === 'open') {
47
+ entry.attempts = 0
48
+ }
49
+ if (update.connection === 'close') {
50
+ const statusCode = update.lastDisconnect?.error?.output?.statusCode
51
+ if (statusCode === DISCONNECT_REASON_LOGGED_OUT) {
52
+ logger?.info?.({ sessionId }, 'session logged out, removing from pool')
53
+ sessions.delete(sessionId)
54
+ options.onLoggedOut?.(sessionId)
55
+ return
56
+ }
57
+ if (entry.stopped) return
58
+ entry.attempts += 1
59
+ const backoffMs = computeBackoffMs(entry.attempts)
60
+ logger?.warn?.(
61
+ { sessionId, attempt: entry.attempts, backoffMs },
62
+ 'session disconnected, reconnecting with backoff'
63
+ )
64
+ setTimeout(() => start(sessionId), backoffMs)
65
+ }
66
+ })
67
+ } catch (error) {
68
+ entry.attempts += 1
69
+ const backoffMs = computeBackoffMs(entry.attempts)
70
+ logger?.error?.({ error, sessionId, backoffMs }, 'failed to start session, retrying with backoff')
71
+ if (!entry.stopped) {
72
+ setTimeout(() => start(sessionId), backoffMs)
73
+ }
74
+ }
75
+ }
76
+
77
+ const add = sessionId => start(sessionId)
78
+ const remove = sessionId => {
79
+ const entry = sessions.get(sessionId)
80
+ if (!entry) return
81
+ entry.stopped = true
82
+ if (entry.sock?.end) {
83
+ try {
84
+ entry.sock.end(undefined)
85
+ } catch {}
86
+ }
87
+ sessions.delete(sessionId)
88
+ }
89
+ const get = sessionId => sessions.get(sessionId)?.sock
90
+ const list = () => [...sessions.keys()]
91
+
92
+ return { add, remove, get, list }
93
+ }
94
+ exports.createSessionPool = createSessionPool