@nexustechpro/baileys 2.0.5 → 2.0.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/WAProto/index.js +22 -18
- package/lib/Defaults/baileys-version.json +1 -1
- package/lib/Defaults/index.js +7 -6
- package/lib/Signal/libsignal.js +65 -50
- package/lib/Socket/chats.js +64 -57
- package/lib/Socket/index.js +2 -3
- package/lib/Socket/messages-recv.js +227 -41
- package/lib/Socket/messages-send.js +79 -117
- package/lib/Socket/nexus-handler.js +325 -90
- package/lib/Socket/registration.js +50 -33
- package/lib/Socket/socket.js +232 -69
- package/lib/Types/Newsletter.js +37 -29
- package/lib/Types/State.js +43 -0
- package/lib/Utils/auth-utils.js +2 -2
- package/lib/Utils/chat-utils.js +48 -16
- package/lib/Utils/companion-reg-client-utils.js +34 -0
- package/lib/Utils/decode-wa-message.js +40 -8
- package/lib/Utils/generics.js +5 -7
- package/lib/Utils/index.js +4 -0
- package/lib/Utils/link-preview.js +10 -0
- package/lib/Utils/messages-media.js +426 -382
- package/lib/Utils/messages.js +602 -487
- package/lib/Utils/process-message.js +53 -35
- package/lib/Utils/reporting-utils.js +155 -0
- package/lib/Utils/signal.js +134 -104
- package/lib/Utils/sync-action-utils.js +33 -0
- package/lib/Utils/tc-token-utils.js +162 -0
- package/lib/WABinary/constants.js +6 -0
- package/lib/WABinary/index.js +1 -0
- package/lib/index.js +2 -3
- package/package.json +6 -4
package/lib/Socket/socket.js
CHANGED
|
@@ -1,16 +1,60 @@
|
|
|
1
|
-
import { Boom } from
|
|
2
|
-
import { randomBytes } from
|
|
3
|
-
import { URL } from
|
|
4
|
-
import { promisify } from
|
|
5
|
-
import { proto } from
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
import { Boom } from '@hapi/boom'
|
|
2
|
+
import { randomBytes } from 'crypto'
|
|
3
|
+
import { URL } from 'url'
|
|
4
|
+
import { promisify } from 'util'
|
|
5
|
+
import { proto } from '../../WAProto/index.js'
|
|
6
|
+
import {
|
|
7
|
+
DEF_CALLBACK_PREFIX,
|
|
8
|
+
DEF_TAG_PREFIX,
|
|
9
|
+
INITIAL_PREKEY_COUNT,
|
|
10
|
+
MIN_PREKEY_COUNT,
|
|
11
|
+
MIN_UPLOAD_INTERVAL,
|
|
12
|
+
NOISE_WA_HEADER,
|
|
13
|
+
UPLOAD_TIMEOUT,
|
|
14
|
+
BATCH_SIZE,
|
|
15
|
+
TimeMs
|
|
16
|
+
} from '../Defaults/index.js'
|
|
17
|
+
import { QueryIds, ReachoutTimelockEnforcementType } from '../Types/index.js'
|
|
18
|
+
import { DisconnectReason, XWAPaths } from '../Types/index.js'
|
|
19
|
+
import {
|
|
20
|
+
addTransactionCapability,
|
|
21
|
+
aesEncryptCTR,
|
|
22
|
+
bindWaitForConnectionUpdate,
|
|
23
|
+
buildPairingQRData,
|
|
24
|
+
bytesToCrockford,
|
|
25
|
+
configureSuccessfulPairing,
|
|
26
|
+
Curve,
|
|
27
|
+
derivePairingCodeKey,
|
|
28
|
+
generateLoginNode,
|
|
29
|
+
generateMdTagPrefix,
|
|
30
|
+
generateRegistrationNode,
|
|
31
|
+
getCodeFromWSError,
|
|
32
|
+
getCompanionPlatformId,
|
|
33
|
+
getErrorCodeFromStreamError,
|
|
34
|
+
getNextPreKeysNode,
|
|
35
|
+
makeEventBuffer,
|
|
36
|
+
makeNoiseHandler,
|
|
37
|
+
promiseTimeout,
|
|
38
|
+
signedKeyPair,
|
|
39
|
+
xmppSignedPreKey
|
|
40
|
+
} from '../Utils/index.js'
|
|
41
|
+
import { getPlatformId, migrateIndexKey } from '../Utils/index.js'
|
|
42
|
+
import {
|
|
43
|
+
assertNodeErrorFree,
|
|
44
|
+
binaryNodeToString,
|
|
45
|
+
encodeBinaryNode,
|
|
46
|
+
getAllBinaryNodeChildren,
|
|
47
|
+
getBinaryNodeChild,
|
|
48
|
+
getBinaryNodeChildren,
|
|
49
|
+
isLidUser,
|
|
50
|
+
jidDecode,
|
|
51
|
+
jidEncode,
|
|
52
|
+
S_WHATSAPP_NET
|
|
53
|
+
} from '../WABinary/index.js'
|
|
54
|
+
import { BinaryInfo } from '../WAM/BinaryInfo.js'
|
|
55
|
+
import { USyncQuery, USyncUser } from '../WAUSync/index.js'
|
|
56
|
+
import { WebSocketClient } from './Client/index.js'
|
|
57
|
+
import { executeWMexQuery } from './mex.js'
|
|
14
58
|
|
|
15
59
|
// ─── Module-scope helpers ──────────────────────────────────────────────────────
|
|
16
60
|
|
|
@@ -43,7 +87,12 @@ export const makeSocket = (config) => {
|
|
|
43
87
|
makeSignalRepository
|
|
44
88
|
} = config
|
|
45
89
|
|
|
46
|
-
if (printQRInTerminal)
|
|
90
|
+
if (printQRInTerminal) {
|
|
91
|
+
logger?.warn(
|
|
92
|
+
{},
|
|
93
|
+
'⚠️ printQRInTerminal is deprecated. Listen to connection.update and handle QR yourself.'
|
|
94
|
+
)
|
|
95
|
+
}
|
|
47
96
|
|
|
48
97
|
const url = typeof waWebSocketUrl === 'string' ? new URL(waWebSocketUrl) : waWebSocketUrl
|
|
49
98
|
|
|
@@ -73,6 +122,7 @@ export const makeSocket = (config) => {
|
|
|
73
122
|
const uqTagId = generateMdTagPrefix()
|
|
74
123
|
const sendPromise = promisify(ws.send)
|
|
75
124
|
|
|
125
|
+
// State
|
|
76
126
|
let epoch = 1
|
|
77
127
|
let lastDateRecv
|
|
78
128
|
let lastUploadTime = 0
|
|
@@ -81,6 +131,10 @@ export const makeSocket = (config) => {
|
|
|
81
131
|
let keepAliveReq
|
|
82
132
|
let qrTimer
|
|
83
133
|
let serverTimeOffsetMs = 0
|
|
134
|
+
let didStartBuffer = false
|
|
135
|
+
|
|
136
|
+
/** Socket end handlers — registered via registerSocketEndHandler() */
|
|
137
|
+
const socketEndHandlers = []
|
|
84
138
|
|
|
85
139
|
const generateMessageTag = () => `${uqTagId}${epoch++}`
|
|
86
140
|
|
|
@@ -153,7 +207,13 @@ export const makeSocket = (config) => {
|
|
|
153
207
|
attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'usync' },
|
|
154
208
|
content: [{
|
|
155
209
|
tag: 'usync',
|
|
156
|
-
attrs: {
|
|
210
|
+
attrs: {
|
|
211
|
+
context: usyncQuery.context,
|
|
212
|
+
mode: usyncQuery.mode,
|
|
213
|
+
sid: generateMessageTag(),
|
|
214
|
+
last: 'true',
|
|
215
|
+
index: '0'
|
|
216
|
+
},
|
|
157
217
|
content: [
|
|
158
218
|
{ tag: 'query', attrs: {}, content: usyncQuery.protocols.map((a) => a.getQueryElement()) },
|
|
159
219
|
{ tag: 'list', attrs: {}, content: userNodes }
|
|
@@ -166,14 +226,30 @@ export const makeSocket = (config) => {
|
|
|
166
226
|
async function pnFromLIDUSync(jids) {
|
|
167
227
|
const usyncQuery = new USyncQuery().withLIDProtocol().withContext('background')
|
|
168
228
|
for (const jid of jids) {
|
|
169
|
-
if (
|
|
170
|
-
|
|
229
|
+
if (isLidUser(jid)) { logger?.warn('LID user found in LID fetch call'); continue }
|
|
230
|
+
usyncQuery.withUser(new USyncUser().withId(jid))
|
|
171
231
|
}
|
|
172
232
|
if (usyncQuery.users.length === 0) return []
|
|
173
233
|
const results = await executeUSyncQuery(usyncQuery)
|
|
174
234
|
return results ? results.list.filter((a) => !!a.lid).map(({ lid, id }) => ({ pn: id, lid })) : []
|
|
175
235
|
}
|
|
176
236
|
|
|
237
|
+
const onWhatsApp = async (...phoneNumbers) => {
|
|
238
|
+
let usyncQuery = new USyncQuery()
|
|
239
|
+
let contactEnabled = false
|
|
240
|
+
for (const jid of phoneNumbers) {
|
|
241
|
+
if (isLidUser(jid)) { logger?.warn('LIDs are not supported with onWhatsApp'); continue }
|
|
242
|
+
if (!contactEnabled) { contactEnabled = true; usyncQuery = usyncQuery.withContactProtocol() }
|
|
243
|
+
const phone = `+${jid.replace('+', '').split('@')[0]?.split(':')[0]}`
|
|
244
|
+
usyncQuery.withUser(new USyncUser().withPhone(phone))
|
|
245
|
+
}
|
|
246
|
+
if (usyncQuery.users.length === 0) return []
|
|
247
|
+
const results = await executeUSyncQuery(usyncQuery)
|
|
248
|
+
return results
|
|
249
|
+
? results.list.filter((a) => !!a.contact).map(({ contact, id }) => ({ jid: id, exists: contact }))
|
|
250
|
+
: []
|
|
251
|
+
}
|
|
252
|
+
|
|
177
253
|
// ─── Pre-keys ───────────────────────────────────────────────────────────────
|
|
178
254
|
|
|
179
255
|
const getAvailablePreKeysOnServer = async () => {
|
|
@@ -185,11 +261,24 @@ export const makeSocket = (config) => {
|
|
|
185
261
|
return +getBinaryNodeChild(result, 'count').attrs.value
|
|
186
262
|
}
|
|
187
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Verify that our current pre-key actually exists in local key storage.
|
|
266
|
+
* Catches the case where the server still has keys but our local store is missing them.
|
|
267
|
+
*/
|
|
268
|
+
const verifyCurrentPreKeyExists = async () => {
|
|
269
|
+
const currentPreKeyId = creds.nextPreKeyId - 1
|
|
270
|
+
if (currentPreKeyId <= 0) return { exists: false, currentPreKeyId: 0 }
|
|
271
|
+
const preKeys = await keys.get('pre-key', [currentPreKeyId.toString()])
|
|
272
|
+
return { exists: !!preKeys[currentPreKeyId.toString()], currentPreKeyId }
|
|
273
|
+
}
|
|
274
|
+
|
|
188
275
|
const uploadPreKeys = async (count = MIN_PREKEY_COUNT, retryCount = 0) => {
|
|
276
|
+
// Rate-limit guard — only on the first attempt, not retries
|
|
189
277
|
if (retryCount === 0 && Date.now() - lastUploadTime < MIN_UPLOAD_INTERVAL) {
|
|
190
|
-
logger.debug(`Skipping upload
|
|
278
|
+
logger.debug(`Skipping upload — only ${Date.now() - lastUploadTime}ms since last upload`)
|
|
191
279
|
return
|
|
192
280
|
}
|
|
281
|
+
// Dedup: if an upload is already in-flight, wait for it
|
|
193
282
|
if (uploadPreKeysPromise) {
|
|
194
283
|
logger.debug('Pre-key upload in progress, waiting...')
|
|
195
284
|
await uploadPreKeysPromise
|
|
@@ -197,6 +286,7 @@ export const makeSocket = (config) => {
|
|
|
197
286
|
}
|
|
198
287
|
const uploadLogic = async () => {
|
|
199
288
|
logger.info({ count, retryCount }, 'Uploading pre-keys')
|
|
289
|
+
// Generate keys inside a transaction to prevent ID collisions on retry
|
|
200
290
|
const node = await keys.transaction(async () => {
|
|
201
291
|
const { update, node } = await getNextPreKeysNode({ creds, keys }, count)
|
|
202
292
|
ev.emit('creds.update', update)
|
|
@@ -230,15 +320,23 @@ export const makeSocket = (config) => {
|
|
|
230
320
|
try {
|
|
231
321
|
const preKeyCount = await getAvailablePreKeysOnServer()
|
|
232
322
|
logger.info(`${preKeyCount} pre-keys found on server`)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
323
|
+
const { exists: currentPreKeyExists, currentPreKeyId } = await verifyCurrentPreKeyExists()
|
|
324
|
+
logger.info(`Current prekey ID: ${currentPreKeyId}, exists in storage: ${currentPreKeyExists}`)
|
|
325
|
+
const lowServerCount = preKeyCount <= MIN_PREKEY_COUNT
|
|
326
|
+
const missingCurrentPreKey = !currentPreKeyExists && currentPreKeyId > 0
|
|
327
|
+
if (lowServerCount || missingCurrentPreKey) {
|
|
328
|
+
const reasons = []
|
|
329
|
+
if (lowServerCount) reasons.push(`server count low (${preKeyCount})`)
|
|
330
|
+
if (missingCurrentPreKey) reasons.push(`current prekey ${currentPreKeyId} missing from storage`)
|
|
331
|
+
logger.info(`Uploading PreKeys due to: ${reasons.join(', ')}`)
|
|
332
|
+
const uploadCount = preKeyCount === 0 ? INITIAL_PREKEY_COUNT : MIN_PREKEY_COUNT
|
|
236
333
|
await uploadPreKeys(uploadCount)
|
|
237
334
|
} else {
|
|
238
|
-
logger.info(`✅ PreKey validation passed
|
|
335
|
+
logger.info(`✅ PreKey validation passed — Server: ${preKeyCount}, prekey ${currentPreKeyId} exists`)
|
|
239
336
|
}
|
|
240
337
|
} catch (error) {
|
|
241
338
|
logger.error({ error }, 'Failed to check/upload pre-keys during init')
|
|
339
|
+
// Non-fatal — allow connection to continue
|
|
242
340
|
}
|
|
243
341
|
}
|
|
244
342
|
|
|
@@ -285,7 +383,7 @@ export const makeSocket = (config) => {
|
|
|
285
383
|
const parsed = Number(tValue)
|
|
286
384
|
if (Number.isNaN(parsed) || parsed <= 0) return
|
|
287
385
|
serverTimeOffsetMs = parsed * 1000 - Date.now()
|
|
288
|
-
logger.debug({ offset: serverTimeOffsetMs }, '
|
|
386
|
+
logger.debug({ offset: serverTimeOffsetMs }, 'Calculated server time offset')
|
|
289
387
|
}
|
|
290
388
|
|
|
291
389
|
// ─── Unified session telemetry ───────────────────────────────────────────────
|
|
@@ -305,14 +403,65 @@ export const makeSocket = (config) => {
|
|
|
305
403
|
content: [{ tag: 'unified_session', attrs: { id: getUnifiedSessionId() } }]
|
|
306
404
|
})
|
|
307
405
|
} catch (error) {
|
|
308
|
-
logger.debug({ error }, '
|
|
406
|
+
logger.debug({ error }, 'Failed to send unified_session telemetry')
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ─── WAM buffer ─────────────────────────────────────────────────────────────
|
|
411
|
+
|
|
412
|
+
const sendWAMBuffer = (wamBuffer) =>
|
|
413
|
+
query({
|
|
414
|
+
tag: 'iq',
|
|
415
|
+
attrs: { to: S_WHATSAPP_NET, id: generateMessageTag(), xmlns: 'w:stats' },
|
|
416
|
+
content: [{ tag: 'add', attrs: { t: Math.round(Date.now() / 1000) + '' }, content: wamBuffer }]
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
// ─── WMex queries ────────────────────────────────────────────────────────────
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Fetches account restriction / reachout timelock status.
|
|
423
|
+
*/
|
|
424
|
+
const fetchAccountReachoutTimelock = async () => {
|
|
425
|
+
const queryResult = await executeWMexQuery(
|
|
426
|
+
{},
|
|
427
|
+
QueryIds.REACHOUT_TIMELOCK,
|
|
428
|
+
XWAPaths.xwa2_fetch_account_reachout_timelock,
|
|
429
|
+
query,
|
|
430
|
+
generateMessageTag
|
|
431
|
+
)
|
|
432
|
+
const result = {
|
|
433
|
+
isActive: !!queryResult?.is_active,
|
|
434
|
+
timeEnforcementEnds:
|
|
435
|
+
queryResult?.time_enforcement_ends && queryResult.time_enforcement_ends !== '0'
|
|
436
|
+
? new Date(parseInt(queryResult.time_enforcement_ends, 10) * 1000)
|
|
437
|
+
: undefined,
|
|
438
|
+
enforcementType: queryResult?.enforcement_type ?? ReachoutTimelockEnforcementType.DEFAULT
|
|
309
439
|
}
|
|
440
|
+
ev.emit('connection.update', { reachoutTimeLock: result })
|
|
441
|
+
return result
|
|
310
442
|
}
|
|
311
443
|
|
|
444
|
+
/**
|
|
445
|
+
* Fetches new-chat message cap quota and usage.
|
|
446
|
+
*/
|
|
447
|
+
const fetchNewChatMessageCap = async () =>
|
|
448
|
+
executeWMexQuery(
|
|
449
|
+
{ input: { type: 'INDIVIDUAL_NEW_CHAT_MSG' } },
|
|
450
|
+
QueryIds.MESSAGE_CAPPING_INFO,
|
|
451
|
+
XWAPaths.xwa2_message_capping_info,
|
|
452
|
+
query,
|
|
453
|
+
generateMessageTag
|
|
454
|
+
)
|
|
455
|
+
|
|
312
456
|
// ─── Connection lifecycle ────────────────────────────────────────────────────
|
|
313
457
|
|
|
314
458
|
const onUnexpectedError = (err, msg) => {
|
|
315
|
-
|
|
459
|
+
const isClosed = err?.message?.includes('Connection Closed') || err?.output?.statusCode === 428
|
|
460
|
+
if (isClosed) {
|
|
461
|
+
logger.debug({ msg: err?.message }, `Connection closed during '${msg}'`)
|
|
462
|
+
} else {
|
|
463
|
+
logger.error({ err }, `unexpected error in '${msg}'`)
|
|
464
|
+
}
|
|
316
465
|
}
|
|
317
466
|
|
|
318
467
|
const awaitNextMessage = async (sendMsg) => {
|
|
@@ -335,7 +484,7 @@ export const makeSocket = (config) => {
|
|
|
335
484
|
}
|
|
336
485
|
|
|
337
486
|
const validateConnection = async () => {
|
|
338
|
-
|
|
487
|
+
const helloMsg = proto.HandshakeMessage.fromObject({ clientHello: { ephemeral: ephemeralKeyPair.public } })
|
|
339
488
|
logger.info({ browser, helloMsg }, 'Connected to WhatsApp')
|
|
340
489
|
const init = proto.HandshakeMessage.encode(helloMsg).finish()
|
|
341
490
|
const result = await awaitNextMessage(init)
|
|
@@ -346,9 +495,7 @@ export const makeSocket = (config) => {
|
|
|
346
495
|
logger.info({ node }, !creds.me ? 'Attempting registration...' : 'Logging in...')
|
|
347
496
|
const payloadEnc = noise.encrypt(proto.ClientPayload.encode(node).finish())
|
|
348
497
|
await sendRawMessage(
|
|
349
|
-
proto.HandshakeMessage.encode({
|
|
350
|
-
clientFinish: { static: keyEnc, payload: payloadEnc }
|
|
351
|
-
}).finish()
|
|
498
|
+
proto.HandshakeMessage.encode({ clientFinish: { static: keyEnc, payload: payloadEnc } }).finish()
|
|
352
499
|
)
|
|
353
500
|
await noise.finishInit()
|
|
354
501
|
startKeepAliveRequest()
|
|
@@ -376,27 +523,32 @@ export const makeSocket = (config) => {
|
|
|
376
523
|
* Keep-alive: ping WA every keepAliveIntervalMs.
|
|
377
524
|
* If the server stops responding (diff > interval + 5s) the connection
|
|
378
525
|
* is considered lost and we call end() — the consumer handles reconnection.
|
|
379
|
-
* No internal reconnect loop — clean separation of concerns.
|
|
380
526
|
*/
|
|
381
527
|
const startKeepAliveRequest = () => {
|
|
382
528
|
keepAliveReq = setInterval(() => {
|
|
383
529
|
if (!lastDateRecv) lastDateRecv = new Date()
|
|
384
530
|
const diff = Date.now() - lastDateRecv.getTime()
|
|
385
531
|
if (diff > keepAliveIntervalMs + 5000) {
|
|
386
|
-
end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
|
|
532
|
+
void end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
|
|
387
533
|
} else if (ws.isOpen) {
|
|
388
534
|
query({
|
|
389
535
|
tag: 'iq',
|
|
390
536
|
attrs: { id: generateMessageTag(), to: S_WHATSAPP_NET, type: 'get', xmlns: 'w:p' },
|
|
391
537
|
content: [{ tag: 'ping', attrs: {} }]
|
|
392
|
-
}).catch((err) => logger.error({ trace: err.stack }, '
|
|
538
|
+
}).catch((err) => logger.error({ trace: err.stack }, 'Error in sending keep alive'))
|
|
393
539
|
} else {
|
|
394
|
-
logger.warn('
|
|
540
|
+
logger.warn('Keep alive called when WS not open')
|
|
395
541
|
}
|
|
396
542
|
}, keepAliveIntervalMs)
|
|
397
543
|
}
|
|
398
544
|
|
|
399
|
-
|
|
545
|
+
/**
|
|
546
|
+
* Tear down the socket.
|
|
547
|
+
* Awaits ws.close() so cleanup is deterministic before emitting connection.update.
|
|
548
|
+
* Runs all registered socketEndHandlers in order.
|
|
549
|
+
* Calls signalRepository.close() and ev.destroy() to prevent leaks.
|
|
550
|
+
*/
|
|
551
|
+
const end = async (error) => {
|
|
400
552
|
if (closed) { logger.trace({ trace: error?.stack }, 'Connection already closed'); return }
|
|
401
553
|
closed = true
|
|
402
554
|
logger.info({ trace: error?.stack }, error ? 'connection errored' : 'connection closed')
|
|
@@ -405,9 +557,17 @@ export const makeSocket = (config) => {
|
|
|
405
557
|
ws.removeAllListeners('close')
|
|
406
558
|
ws.removeAllListeners('open')
|
|
407
559
|
ws.removeAllListeners('message')
|
|
408
|
-
|
|
560
|
+
signalRepository.close?.()
|
|
561
|
+
if (!ws.isClosed && !ws.isClosing) {
|
|
562
|
+
try { await ws.close() } catch { }
|
|
563
|
+
}
|
|
564
|
+
for (const handler of socketEndHandlers) {
|
|
565
|
+
try { await handler(error) }
|
|
566
|
+
catch (err) { logger.error({ err }, 'error in socket end handler') }
|
|
567
|
+
}
|
|
409
568
|
ev.emit('connection.update', { connection: 'close', lastDisconnect: { error, date: new Date() } })
|
|
410
569
|
ev.removeAllListeners('connection.update')
|
|
570
|
+
ev.destroy()
|
|
411
571
|
}
|
|
412
572
|
|
|
413
573
|
const sendPassiveIq = (tag) =>
|
|
@@ -426,14 +586,21 @@ export const makeSocket = (config) => {
|
|
|
426
586
|
content: [{ tag: 'remove-companion-device', attrs: { jid, reason: 'user_initiated' } }]
|
|
427
587
|
})
|
|
428
588
|
}
|
|
429
|
-
end(new Boom(msg || 'Intentional Logout', { statusCode: DisconnectReason.loggedOut }))
|
|
589
|
+
void end(new Boom(msg || 'Intentional Logout', { statusCode: DisconnectReason.loggedOut }))
|
|
430
590
|
}
|
|
431
591
|
|
|
592
|
+
/**
|
|
593
|
+
* Register a cleanup handler that will be awaited during end().
|
|
594
|
+
* Use for releasing external resources tied to this socket's lifetime.
|
|
595
|
+
*/
|
|
596
|
+
const registerSocketEndHandler = (handler) => socketEndHandlers.push(handler)
|
|
597
|
+
|
|
432
598
|
// ─── Pairing ─────────────────────────────────────────────────────────────────
|
|
433
599
|
|
|
434
600
|
const requestPairingCode = async (phoneNumber, customPairingCode) => {
|
|
435
601
|
await waitForSocketOpen()
|
|
436
|
-
|
|
602
|
+
// Brief stabilisation delay — ensures the WS open event has fully propagated
|
|
603
|
+
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
437
604
|
const pairingCode = customPairingCode ?? bytesToCrockford(randomBytes(5))
|
|
438
605
|
if (customPairingCode && customPairingCode?.length !== 8)
|
|
439
606
|
throw new Error('Custom pairing code must be exactly 8 chars')
|
|
@@ -449,7 +616,7 @@ export const makeSocket = (config) => {
|
|
|
449
616
|
content: [
|
|
450
617
|
{ tag: 'link_code_pairing_wrapped_companion_ephemeral_pub', attrs: {}, content: await generatePairingKey() },
|
|
451
618
|
{ tag: 'companion_server_auth_key_pub', attrs: {}, content: authState.creds.noiseKey.public },
|
|
452
|
-
{ tag: 'companion_platform_id', attrs: {}, content:
|
|
619
|
+
{ tag: 'companion_platform_id', attrs: {}, content: getCompanionPlatformId(browser) },
|
|
453
620
|
{ tag: 'companion_platform_display', attrs: {}, content: `${browser[1]} (${browser[0]})` },
|
|
454
621
|
{ tag: 'link_code_pairing_nonce', attrs: {}, content: '0' }
|
|
455
622
|
]
|
|
@@ -492,31 +659,22 @@ export const makeSocket = (config) => {
|
|
|
492
659
|
})
|
|
493
660
|
}
|
|
494
661
|
|
|
495
|
-
const sendWAMBuffer = (wamBuffer) =>
|
|
496
|
-
query({
|
|
497
|
-
tag: 'iq',
|
|
498
|
-
attrs: { to: S_WHATSAPP_NET, id: generateMessageTag(), xmlns: 'w:stats' },
|
|
499
|
-
content: [{ tag: 'add', attrs: { t: Math.round(Date.now() / 1000) + '' }, content: wamBuffer }]
|
|
500
|
-
})
|
|
501
|
-
|
|
502
662
|
// ─── WebSocket event bindings ────────────────────────────────────────────────
|
|
503
663
|
|
|
504
664
|
ws.on('message', onMessageReceived)
|
|
505
665
|
|
|
506
666
|
ws.on('open', async () => {
|
|
507
667
|
try { await validateConnection() }
|
|
508
|
-
catch (err) { logger.error({ err }, 'error in validating connection'); end(err) }
|
|
668
|
+
catch (err) { logger.error({ err }, 'error in validating connection'); void end(err) }
|
|
509
669
|
})
|
|
510
670
|
|
|
511
|
-
// Let mapWebSocketError convert the raw error then call end()
|
|
512
671
|
ws.on('error', mapWebSocketError(end))
|
|
513
672
|
|
|
514
|
-
|
|
515
|
-
ws.on('close', () => end(new Boom('Connection Terminated', { statusCode: DisconnectReason.connectionClosed })))
|
|
673
|
+
ws.on('close', () => void end(new Boom('Connection Terminated', { statusCode: DisconnectReason.connectionClosed })))
|
|
516
674
|
|
|
517
675
|
ws.on('CB:xmlstreamend', () => {
|
|
518
676
|
logger.info('Stream ended by server')
|
|
519
|
-
if (!closed) end(new Boom('Connection Terminated by Server', { statusCode: DisconnectReason.connectionClosed }))
|
|
677
|
+
if (!closed) void end(new Boom('Connection Terminated by Server', { statusCode: DisconnectReason.connectionClosed }))
|
|
520
678
|
})
|
|
521
679
|
|
|
522
680
|
// ─── QR pairing ─────────────────────────────────────────────────────────────
|
|
@@ -532,9 +690,10 @@ export const makeSocket = (config) => {
|
|
|
532
690
|
const genPairQR = () => {
|
|
533
691
|
if (!ws.isOpen) return
|
|
534
692
|
const refNode = refNodes.shift()
|
|
535
|
-
if (!refNode) { end(new Boom('QR refs attempts ended', { statusCode: DisconnectReason.timedOut })); return }
|
|
693
|
+
if (!refNode) { void end(new Boom('QR refs attempts ended', { statusCode: DisconnectReason.timedOut })); return }
|
|
536
694
|
const ref = refNode.content.toString('utf-8')
|
|
537
|
-
|
|
695
|
+
// Use buildPairingQRData so the browser tuple is included in the QR payload
|
|
696
|
+
const qr = buildPairingQRData(ref, noiseKeyB64, identityKeyB64, advB64, browser)
|
|
538
697
|
ev.emit('connection.update', { qr })
|
|
539
698
|
qrTimer = setTimeout(genPairQR, qrMs)
|
|
540
699
|
qrMs = qrTimeout || 20000
|
|
@@ -554,7 +713,7 @@ export const makeSocket = (config) => {
|
|
|
554
713
|
void sendUnifiedSession()
|
|
555
714
|
} catch (error) {
|
|
556
715
|
logger.info({ trace: error.stack }, 'Error in pairing')
|
|
557
|
-
end(error)
|
|
716
|
+
void end(error)
|
|
558
717
|
}
|
|
559
718
|
})
|
|
560
719
|
|
|
@@ -565,15 +724,11 @@ export const makeSocket = (config) => {
|
|
|
565
724
|
updateServerTimeOffset(node)
|
|
566
725
|
await uploadPreKeysToServerIfRequired()
|
|
567
726
|
await sendPassiveIq('active')
|
|
568
|
-
try {
|
|
569
|
-
|
|
570
|
-
} catch (e) {
|
|
571
|
-
logger.warn({ e }, 'failed to run digest after login')
|
|
572
|
-
}
|
|
727
|
+
try { await digestKeyBundle() }
|
|
728
|
+
catch (e) { logger.warn({ e }, 'failed to run digest after login') }
|
|
573
729
|
} catch (err) {
|
|
574
730
|
logger.warn({ err }, 'Failed to send initial passive IQ')
|
|
575
731
|
}
|
|
576
|
-
|
|
577
732
|
logger.info('✅ Opened connection to WhatsApp')
|
|
578
733
|
clearTimeout(qrTimer)
|
|
579
734
|
ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } })
|
|
@@ -585,18 +740,22 @@ export const makeSocket = (config) => {
|
|
|
585
740
|
process.nextTick(async () => {
|
|
586
741
|
try {
|
|
587
742
|
const myPN = authState.creds.me.id
|
|
743
|
+
// Store own LID-PN mapping
|
|
588
744
|
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: myLID, pn: myPN }])
|
|
745
|
+
// Build device-list using index-based batching to avoid unbounded key growth
|
|
589
746
|
const { user, device } = jidDecode(myPN)
|
|
590
747
|
const currentBatch = await migrateIndexKey(authState.keys, 'device-list')
|
|
591
748
|
currentBatch[user] = [device?.toString() || '0']
|
|
592
749
|
const deviceKeys = Object.keys(currentBatch)
|
|
593
750
|
if (deviceKeys.length > BATCH_SIZE) {
|
|
594
751
|
deviceKeys.sort()
|
|
595
|
-
deviceKeys.slice(0, deviceKeys.length - BATCH_SIZE).forEach(k => delete currentBatch[k])
|
|
752
|
+
deviceKeys.slice(0, deviceKeys.length - BATCH_SIZE).forEach((k) => delete currentBatch[k])
|
|
596
753
|
}
|
|
597
|
-
await authState.keys.set({ 'device-list': {
|
|
754
|
+
await authState.keys.set({ 'device-list': { index: currentBatch } })
|
|
755
|
+
// Migrate own session from PN → LID
|
|
598
756
|
await signalRepository.migrateSession(myPN, myLID)
|
|
599
757
|
logger.info({ myPN, myLID }, 'Own LID session created successfully')
|
|
758
|
+
// Batch-migrate any remaining PN sessions to LID
|
|
600
759
|
if (signalRepository.migrateAllPNSessionsToLID) {
|
|
601
760
|
try {
|
|
602
761
|
const migrated = await signalRepository.migrateAllPNSessionsToLID()
|
|
@@ -615,37 +774,37 @@ export const makeSocket = (config) => {
|
|
|
615
774
|
// ─── Stream / connection error handlers ─────────────────────────────────────
|
|
616
775
|
|
|
617
776
|
ws.on('CB:stream:error', (node) => {
|
|
618
|
-
|
|
777
|
+
const [reasonNode] = getAllBinaryNodeChildren(node)
|
|
778
|
+
logger.error({ reasonNode, fullErrorNode: node }, 'Stream errored out')
|
|
619
779
|
const { reason, statusCode } = getErrorCodeFromStreamError(node)
|
|
620
|
-
end(new Boom(`Stream Errored (${reason})`, { statusCode, data: node }))
|
|
780
|
+
void end(new Boom(`Stream Errored (${reason})`, { statusCode, data: reasonNode || node }))
|
|
621
781
|
})
|
|
622
782
|
|
|
623
783
|
ws.on('CB:failure', (node) => {
|
|
624
784
|
const reason = +(node.attrs.reason || 500)
|
|
625
|
-
end(new Boom('Connection Failure', { statusCode: reason, data: node.attrs }))
|
|
785
|
+
void end(new Boom('Connection Failure', { statusCode: reason, data: node.attrs }))
|
|
626
786
|
})
|
|
627
787
|
|
|
628
788
|
ws.on('CB:ib,,downgrade_webclient', () =>
|
|
629
|
-
end(new Boom('Multi-device beta not joined', { statusCode: DisconnectReason.multideviceMismatch }))
|
|
789
|
+
void end(new Boom('Multi-device beta not joined', { statusCode: DisconnectReason.multideviceMismatch }))
|
|
630
790
|
)
|
|
631
791
|
|
|
632
|
-
ws.on('CB:ib,,offline_preview', (node) => {
|
|
792
|
+
ws.on('CB:ib,,offline_preview', async (node) => {
|
|
633
793
|
logger.info('Offline preview received', JSON.stringify(node))
|
|
634
|
-
sendNode({ tag: 'ib', attrs: {}, content: [{ tag: 'offline_batch', attrs: { count: '100' } }] })
|
|
794
|
+
await sendNode({ tag: 'ib', attrs: {}, content: [{ tag: 'offline_batch', attrs: { count: '100' } }] })
|
|
635
795
|
})
|
|
636
796
|
|
|
637
797
|
ws.on('CB:ib,,edge_routing', (node) => {
|
|
638
798
|
const edgeRoutingNode = getBinaryNodeChild(node, 'edge_routing')
|
|
639
799
|
const routingInfo = getBinaryNodeChild(edgeRoutingNode, 'routing_info')
|
|
640
800
|
if (routingInfo?.content) {
|
|
641
|
-
authState.creds.routingInfo = Buffer.from(routingInfo
|
|
801
|
+
authState.creds.routingInfo = Buffer.from(routingInfo.content)
|
|
642
802
|
ev.emit('creds.update', authState.creds)
|
|
643
803
|
}
|
|
644
804
|
})
|
|
645
805
|
|
|
646
806
|
// ─── Buffering & offline notifications ──────────────────────────────────────
|
|
647
807
|
|
|
648
|
-
let didStartBuffer = false
|
|
649
808
|
process.nextTick(() => {
|
|
650
809
|
if (creds.me?.id) { ev.buffer(); didStartBuffer = true }
|
|
651
810
|
ev.emit('connection.update', { connection: 'connecting', receivedPendingNotifications: false, qr: undefined })
|
|
@@ -689,6 +848,7 @@ export const makeSocket = (config) => {
|
|
|
689
848
|
sendNode,
|
|
690
849
|
logout,
|
|
691
850
|
end,
|
|
851
|
+
registerSocketEndHandler,
|
|
692
852
|
onUnexpectedError,
|
|
693
853
|
uploadPreKeys,
|
|
694
854
|
uploadPreKeysToServerIfRequired,
|
|
@@ -701,6 +861,9 @@ export const makeSocket = (config) => {
|
|
|
701
861
|
waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),
|
|
702
862
|
sendWAMBuffer,
|
|
703
863
|
executeUSyncQuery,
|
|
864
|
+
onWhatsApp,
|
|
865
|
+
fetchAccountReachoutTimelock,
|
|
866
|
+
fetchNewChatMessageCap,
|
|
704
867
|
listener: (eventName) => {
|
|
705
868
|
if (typeof ev.listenerCount === 'function') return ev.listenerCount(eventName)
|
|
706
869
|
if (typeof ev.listener === 'function') return ev.listener(eventName)?.length || 0
|
package/lib/Types/Newsletter.js
CHANGED
|
@@ -1,29 +1,37 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
export var XWAPaths;
|
|
2
|
+
(function (XWAPaths) {
|
|
3
|
+
XWAPaths["xwa2_newsletter_create"] = "xwa2_newsletter_create";
|
|
4
|
+
XWAPaths["xwa2_newsletter_subscribers"] = "xwa2_newsletter_subscribers";
|
|
5
|
+
XWAPaths["xwa2_newsletter_view"] = "xwa2_newsletter_view";
|
|
6
|
+
XWAPaths["xwa2_newsletter_metadata"] = "xwa2_newsletter";
|
|
7
|
+
XWAPaths["xwa2_newsletter_admin_count"] = "xwa2_newsletter_admin";
|
|
8
|
+
XWAPaths["xwa2_newsletter_mute_v2"] = "xwa2_newsletter_mute_v2";
|
|
9
|
+
XWAPaths["xwa2_newsletter_unmute_v2"] = "xwa2_newsletter_unmute_v2";
|
|
10
|
+
XWAPaths["xwa2_newsletter_follow"] = "xwa2_newsletter_follow";
|
|
11
|
+
XWAPaths["xwa2_newsletter_unfollow"] = "xwa2_newsletter_unfollow";
|
|
12
|
+
XWAPaths["xwa2_newsletter_join_v2"] = "xwa2_newsletter_join_v2";
|
|
13
|
+
XWAPaths["xwa2_newsletter_leave_v2"] = "xwa2_newsletter_leave_v2";
|
|
14
|
+
XWAPaths["xwa2_newsletter_change_owner"] = "xwa2_newsletter_change_owner";
|
|
15
|
+
XWAPaths["xwa2_newsletter_demote"] = "xwa2_newsletter_demote";
|
|
16
|
+
XWAPaths["xwa2_newsletter_delete_v2"] = "xwa2_newsletter_delete_v2";
|
|
17
|
+
XWAPaths["xwa2_fetch_account_reachout_timelock"] = "xwa2_fetch_account_reachout_timelock";
|
|
18
|
+
XWAPaths["xwa2_message_capping_info"] = "xwa2_message_capping_info";
|
|
19
|
+
})(XWAPaths || (XWAPaths = {}));
|
|
20
|
+
export var QueryIds;
|
|
21
|
+
(function (QueryIds) {
|
|
22
|
+
QueryIds["CREATE"] = "8823471724422422";
|
|
23
|
+
QueryIds["UPDATE_METADATA"] = "24250201037901610";
|
|
24
|
+
QueryIds["METADATA"] = "6563316087068696";
|
|
25
|
+
QueryIds["SUBSCRIBERS"] = "9783111038412085";
|
|
26
|
+
QueryIds["FOLLOW"] = "24404358912487870";
|
|
27
|
+
QueryIds["UNFOLLOW"] = "9767147403369991";
|
|
28
|
+
QueryIds["MUTE"] = "29766401636284406";
|
|
29
|
+
QueryIds["UNMUTE"] = "9864994326891137";
|
|
30
|
+
QueryIds["ADMIN_COUNT"] = "7130823597031706";
|
|
31
|
+
QueryIds["CHANGE_OWNER"] = "7341777602580933";
|
|
32
|
+
QueryIds["DEMOTE"] = "6551828931592903";
|
|
33
|
+
QueryIds["DELETE"] = "30062808666639665";
|
|
34
|
+
QueryIds["REACHOUT_TIMELOCK"] = "23983697327930364";
|
|
35
|
+
QueryIds["MESSAGE_CAPPING_INFO"] = "24503548349331633";
|
|
36
|
+
})(QueryIds || (QueryIds = {}));
|
|
37
|
+
//# sourceMappingURL=Mex.js.map
|