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