@nexustechpro/baileys 1.0.2 → 1.0.3
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/lib/Defaults/index.js +1 -1
- package/lib/Socket/Client/websocket.js +116 -85
- package/lib/Socket/socket.js +375 -530
- package/lib/Store/index.js +2 -1
- package/lib/index.js +53 -7
- package/package.json +1 -1
package/lib/Socket/socket.js
CHANGED
|
@@ -4,96 +4,79 @@ import { URL } from "url"
|
|
|
4
4
|
import { promisify } from "util"
|
|
5
5
|
import { proto } from "../../WAProto/index.js"
|
|
6
6
|
import {
|
|
7
|
-
DEF_CALLBACK_PREFIX,
|
|
8
|
-
|
|
9
|
-
INITIAL_PREKEY_COUNT,
|
|
10
|
-
MIN_PREKEY_COUNT,
|
|
11
|
-
MIN_UPLOAD_INTERVAL,
|
|
12
|
-
NOISE_WA_HEADER,
|
|
13
|
-
UPLOAD_TIMEOUT,
|
|
7
|
+
DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT,
|
|
8
|
+
MIN_UPLOAD_INTERVAL, NOISE_WA_HEADER, UPLOAD_TIMEOUT
|
|
14
9
|
} from "../Defaults/index.js"
|
|
15
10
|
import { DisconnectReason } from "../Types/index.js"
|
|
16
11
|
import {
|
|
17
|
-
addTransactionCapability,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
configureSuccessfulPairing,
|
|
22
|
-
Curve,
|
|
23
|
-
derivePairingCodeKey,
|
|
24
|
-
generateLoginNode,
|
|
25
|
-
generateMdTagPrefix,
|
|
26
|
-
generateRegistrationNode,
|
|
27
|
-
getCodeFromWSError,
|
|
28
|
-
getErrorCodeFromStreamError,
|
|
29
|
-
getNextPreKeysNode,
|
|
30
|
-
makeEventBuffer,
|
|
31
|
-
makeNoiseHandler,
|
|
32
|
-
promiseTimeout,
|
|
12
|
+
addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, bytesToCrockford,
|
|
13
|
+
configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix,
|
|
14
|
+
generateRegistrationNode, getCodeFromWSError, getErrorCodeFromStreamError, getNextPreKeysNode,
|
|
15
|
+
makeEventBuffer, makeNoiseHandler, promiseTimeout
|
|
33
16
|
} from "../Utils/index.js"
|
|
34
17
|
import { getPlatformId } from "../Utils/browser-utils.js"
|
|
35
18
|
import {
|
|
36
|
-
assertNodeErrorFree,
|
|
37
|
-
|
|
38
|
-
encodeBinaryNode,
|
|
39
|
-
getBinaryNodeChild,
|
|
40
|
-
getBinaryNodeChildren,
|
|
41
|
-
isLidUser,
|
|
42
|
-
jidDecode,
|
|
43
|
-
jidEncode,
|
|
44
|
-
S_WHATSAPP_NET,
|
|
19
|
+
assertNodeErrorFree, binaryNodeToString, encodeBinaryNode, getBinaryNodeChild,
|
|
20
|
+
getBinaryNodeChildren, isLidUser, jidDecode, jidEncode, S_WHATSAPP_NET
|
|
45
21
|
} from "../WABinary/index.js"
|
|
46
22
|
import { BinaryInfo } from "../WAM/BinaryInfo.js"
|
|
47
23
|
import { USyncQuery, USyncUser } from "../WAUSync/index.js"
|
|
48
24
|
import { WebSocketClient } from "./Client/index.js"
|
|
49
|
-
|
|
50
|
-
* Connects to WA servers and performs:
|
|
51
|
-
* - simple queries (no retry mechanism, wait for connection establishment)
|
|
52
|
-
* - listen to messages and emit events
|
|
53
|
-
* - query phone connection
|
|
54
|
-
*/
|
|
25
|
+
|
|
55
26
|
export const makeSocket = (config) => {
|
|
56
27
|
const {
|
|
57
|
-
waWebSocketUrl,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
keepAliveIntervalMs,
|
|
61
|
-
browser,
|
|
62
|
-
auth: authState,
|
|
63
|
-
printQRInTerminal,
|
|
64
|
-
defaultQueryTimeoutMs,
|
|
65
|
-
transactionOpts,
|
|
66
|
-
qrTimeout,
|
|
67
|
-
makeSignalRepository,
|
|
28
|
+
waWebSocketUrl, connectTimeoutMs, logger, keepAliveIntervalMs, browser,
|
|
29
|
+
auth: authState, printQRInTerminal, defaultQueryTimeoutMs, transactionOpts,
|
|
30
|
+
qrTimeout, makeSignalRepository
|
|
68
31
|
} = config
|
|
32
|
+
|
|
69
33
|
const publicWAMBuffer = new BinaryInfo()
|
|
70
34
|
const uqTagId = generateMdTagPrefix()
|
|
35
|
+
let epoch = 1
|
|
71
36
|
const generateMessageTag = () => `${uqTagId}${epoch++}`
|
|
37
|
+
|
|
72
38
|
if (printQRInTerminal) {
|
|
73
|
-
|
|
74
|
-
"⚠️ The printQRInTerminal option has been deprecated. You will no longer receive QR codes in the terminal automatically. Please listen to the connection.update event yourself and handle the QR your way. You can remove this message by removing this opttion. This message will be removed in a future version.",
|
|
75
|
-
)
|
|
39
|
+
logger?.warn("printQRInTerminal deprecated - handle QR via connection.update event")
|
|
76
40
|
}
|
|
41
|
+
|
|
77
42
|
const url = typeof waWebSocketUrl === "string" ? new URL(waWebSocketUrl) : waWebSocketUrl
|
|
78
43
|
if (config.mobile || url.protocol === "tcp:") {
|
|
79
|
-
throw new Boom("Mobile API
|
|
44
|
+
throw new Boom("Mobile API not supported", { statusCode: DisconnectReason.loggedOut })
|
|
80
45
|
}
|
|
81
46
|
if (url.protocol === "wss" && authState?.creds?.routingInfo) {
|
|
82
47
|
url.searchParams.append("ED", authState.creds.routingInfo.toString("base64url"))
|
|
83
48
|
}
|
|
84
|
-
|
|
49
|
+
|
|
85
50
|
const ephemeralKeyPair = Curve.generateKeyPair()
|
|
86
|
-
/** WA noise protocol wrapper */
|
|
87
51
|
const noise = makeNoiseHandler({
|
|
88
52
|
keyPair: ephemeralKeyPair,
|
|
89
53
|
NOISE_HEADER: NOISE_WA_HEADER,
|
|
90
54
|
logger,
|
|
91
|
-
routingInfo: authState?.creds?.routingInfo
|
|
55
|
+
routingInfo: authState?.creds?.routingInfo
|
|
92
56
|
})
|
|
57
|
+
|
|
93
58
|
const ws = new WebSocketClient(url, config)
|
|
94
59
|
ws.connect()
|
|
60
|
+
|
|
61
|
+
const ev = makeEventBuffer(logger)
|
|
62
|
+
const { creds } = authState
|
|
63
|
+
const keys = addTransactionCapability(authState.keys, logger, transactionOpts)
|
|
64
|
+
const signalRepository = makeSignalRepository({ creds, keys }, logger, pnFromLIDUSync)
|
|
65
|
+
|
|
66
|
+
let lastDateRecv, keepAliveReq, qrTimer, sessionHealthCheck, preKeyMonitorInterval
|
|
67
|
+
let closed = false, isUploadingPreKeys = false
|
|
68
|
+
let lastPreKeyCheck = 0, lastUploadTime = 0, uploadPreKeysPromise = null
|
|
69
|
+
let lastMessageTime = Date.now()
|
|
70
|
+
let preKeyCheckQueue = []
|
|
71
|
+
let reconnectAttempts = 0
|
|
72
|
+
const MAX_RECONNECT_ATTEMPTS = 5
|
|
73
|
+
|
|
74
|
+
const PREKEY_CHECK_INTERVAL = 30 * 60 * 1000 // 30 mins
|
|
75
|
+
const PREKEY_MIN_INTERVAL = 5 * 60 * 1000 // 5 mins
|
|
76
|
+
const PREKEY_CRITICAL_THRESHOLD = 3
|
|
77
|
+
|
|
95
78
|
const sendPromise = promisify(ws.send)
|
|
96
|
-
|
|
79
|
+
|
|
97
80
|
const sendRawMessage = async (data) => {
|
|
98
81
|
if (!ws.isOpen) {
|
|
99
82
|
throw new Boom("Connection Closed", { statusCode: DisconnectReason.connectionClosed })
|
|
@@ -108,7 +91,7 @@ export const makeSocket = (config) => {
|
|
|
108
91
|
}
|
|
109
92
|
})
|
|
110
93
|
}
|
|
111
|
-
|
|
94
|
+
|
|
112
95
|
const sendNode = (frame) => {
|
|
113
96
|
if (logger.level === "trace") {
|
|
114
97
|
logger.trace({ xml: binaryNodeToString(frame), msg: "xml send" })
|
|
@@ -116,27 +99,15 @@ export const makeSocket = (config) => {
|
|
|
116
99
|
const buff = encodeBinaryNode(frame)
|
|
117
100
|
return sendRawMessage(buff)
|
|
118
101
|
}
|
|
119
|
-
|
|
120
|
-
* Wait for a message with a certain tag to be received
|
|
121
|
-
* @param msgId the message tag to await
|
|
122
|
-
* @param timeoutMs timeout after which the promise will reject
|
|
123
|
-
*/
|
|
102
|
+
|
|
124
103
|
const waitForMessage = async (msgId, timeoutMs = defaultQueryTimeoutMs) => {
|
|
125
|
-
let onRecv
|
|
126
|
-
let onErr
|
|
104
|
+
let onRecv, onErr
|
|
127
105
|
try {
|
|
128
106
|
const result = await promiseTimeout(timeoutMs, (resolve, reject) => {
|
|
129
|
-
onRecv = (data) =>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
reject(
|
|
134
|
-
err ||
|
|
135
|
-
new Boom("Connection Closed", {
|
|
136
|
-
statusCode: DisconnectReason.connectionClosed,
|
|
137
|
-
}),
|
|
138
|
-
)
|
|
139
|
-
}
|
|
107
|
+
onRecv = (data) => resolve(data)
|
|
108
|
+
onErr = (err) => reject(err || new Boom("Connection Closed", {
|
|
109
|
+
statusCode: DisconnectReason.connectionClosed
|
|
110
|
+
}))
|
|
140
111
|
ws.on(`TAG:${msgId}`, onRecv)
|
|
141
112
|
ws.on("close", onErr)
|
|
142
113
|
ws.on("error", onErr)
|
|
@@ -144,7 +115,6 @@ export const makeSocket = (config) => {
|
|
|
144
115
|
})
|
|
145
116
|
return result
|
|
146
117
|
} catch (error) {
|
|
147
|
-
// Catch timeout and return undefined instead of throwing
|
|
148
118
|
if (error instanceof Boom && error.output?.statusCode === DisconnectReason.timedOut) {
|
|
149
119
|
logger?.warn?.({ msgId }, "timed out waiting for message")
|
|
150
120
|
return undefined
|
|
@@ -158,79 +128,55 @@ export const makeSocket = (config) => {
|
|
|
158
128
|
}
|
|
159
129
|
}
|
|
160
130
|
}
|
|
161
|
-
|
|
131
|
+
|
|
162
132
|
const query = async (node, timeoutMs) => {
|
|
163
|
-
if (!node.attrs.id)
|
|
164
|
-
node.attrs.id = generateMessageTag()
|
|
165
|
-
}
|
|
133
|
+
if (!node.attrs.id) node.attrs.id = generateMessageTag()
|
|
166
134
|
const msgId = node.attrs.id
|
|
167
135
|
const result = await promiseTimeout(timeoutMs, async (resolve, reject) => {
|
|
168
136
|
const result = waitForMessage(msgId, timeoutMs).catch(reject)
|
|
169
|
-
sendNode(node)
|
|
170
|
-
.then(async () => resolve(await result))
|
|
171
|
-
.catch(reject)
|
|
137
|
+
sendNode(node).then(async () => resolve(await result)).catch(reject)
|
|
172
138
|
})
|
|
173
139
|
if (result && "tag" in result) {
|
|
174
140
|
assertNodeErrorFree(result)
|
|
175
141
|
}
|
|
176
142
|
return result
|
|
177
143
|
}
|
|
144
|
+
|
|
178
145
|
const executeUSyncQuery = async (usyncQuery) => {
|
|
179
146
|
if (usyncQuery.protocols.length === 0) {
|
|
180
147
|
throw new Boom("USyncQuery must have at least one protocol")
|
|
181
148
|
}
|
|
182
|
-
// todo: validate users, throw WARNING on no valid users
|
|
183
|
-
// variable below has only validated users
|
|
184
149
|
const validUsers = usyncQuery.users
|
|
185
|
-
const userNodes = validUsers.map((user) => {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
},
|
|
191
|
-
content: usyncQuery.protocols.map((a) => a.getUserElement(user)).filter((a) => a !== null),
|
|
192
|
-
}
|
|
193
|
-
})
|
|
194
|
-
const listNode = {
|
|
195
|
-
tag: "list",
|
|
196
|
-
attrs: {},
|
|
197
|
-
content: userNodes,
|
|
198
|
-
}
|
|
199
|
-
const queryNode = {
|
|
200
|
-
tag: "query",
|
|
201
|
-
attrs: {},
|
|
202
|
-
content: usyncQuery.protocols.map((a) => a.getQueryElement()),
|
|
203
|
-
}
|
|
150
|
+
const userNodes = validUsers.map((user) => ({
|
|
151
|
+
tag: "user",
|
|
152
|
+
attrs: { jid: !user.phone ? user.id : undefined },
|
|
153
|
+
content: usyncQuery.protocols.map((a) => a.getUserElement(user)).filter((a) => a !== null)
|
|
154
|
+
}))
|
|
204
155
|
const iq = {
|
|
205
156
|
tag: "iq",
|
|
206
|
-
attrs: {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
{
|
|
213
|
-
tag: "usync",
|
|
214
|
-
attrs: {
|
|
215
|
-
context: usyncQuery.context,
|
|
216
|
-
mode: usyncQuery.mode,
|
|
217
|
-
sid: generateMessageTag(),
|
|
218
|
-
last: "true",
|
|
219
|
-
index: "0",
|
|
220
|
-
},
|
|
221
|
-
content: [queryNode, listNode],
|
|
157
|
+
attrs: { to: S_WHATSAPP_NET, type: "get", xmlns: "usync" },
|
|
158
|
+
content: [{
|
|
159
|
+
tag: "usync",
|
|
160
|
+
attrs: {
|
|
161
|
+
context: usyncQuery.context, mode: usyncQuery.mode,
|
|
162
|
+
sid: generateMessageTag(), last: "true", index: "0"
|
|
222
163
|
},
|
|
223
|
-
|
|
164
|
+
content: [
|
|
165
|
+
{ tag: "query", attrs: {}, content: usyncQuery.protocols.map((a) => a.getQueryElement()) },
|
|
166
|
+
{ tag: "list", attrs: {}, content: userNodes }
|
|
167
|
+
]
|
|
168
|
+
}]
|
|
224
169
|
}
|
|
225
170
|
const result = await query(iq)
|
|
226
171
|
return usyncQuery.parseUSyncQueryResult(result)
|
|
227
172
|
}
|
|
173
|
+
|
|
228
174
|
const onWhatsApp = async (...phoneNumber) => {
|
|
229
175
|
let usyncQuery = new USyncQuery()
|
|
230
176
|
let contactEnabled = false
|
|
231
177
|
for (const jid of phoneNumber) {
|
|
232
178
|
if (isLidUser(jid)) {
|
|
233
|
-
logger?.warn("LIDs
|
|
179
|
+
logger?.warn("LIDs not supported with onWhatsApp")
|
|
234
180
|
continue
|
|
235
181
|
} else {
|
|
236
182
|
if (!contactEnabled) {
|
|
@@ -241,15 +187,16 @@ export const makeSocket = (config) => {
|
|
|
241
187
|
usyncQuery.withUser(new USyncUser().withPhone(phone))
|
|
242
188
|
}
|
|
243
189
|
}
|
|
244
|
-
if (usyncQuery.users.length === 0)
|
|
245
|
-
return [] // return early without forcing an empty query
|
|
246
|
-
}
|
|
190
|
+
if (usyncQuery.users.length === 0) return []
|
|
247
191
|
const results = await executeUSyncQuery(usyncQuery)
|
|
248
192
|
if (results) {
|
|
249
|
-
return results.list.filter((a) => !!a.contact).map(({ contact, id }) => ({
|
|
193
|
+
return results.list.filter((a) => !!a.contact).map(({ contact, id }) => ({
|
|
194
|
+
jid: id, exists: contact
|
|
195
|
+
}))
|
|
250
196
|
}
|
|
251
197
|
}
|
|
252
|
-
|
|
198
|
+
|
|
199
|
+
async function pnFromLIDUSync(jids) {
|
|
253
200
|
const usyncQuery = new USyncQuery().withLIDProtocol().withContext("background")
|
|
254
201
|
for (const jid of jids) {
|
|
255
202
|
if (isLidUser(jid)) {
|
|
@@ -259,43 +206,32 @@ export const makeSocket = (config) => {
|
|
|
259
206
|
usyncQuery.withUser(new USyncUser().withId(jid))
|
|
260
207
|
}
|
|
261
208
|
}
|
|
262
|
-
if (usyncQuery.users.length === 0)
|
|
263
|
-
return [] // return early without forcing an empty query
|
|
264
|
-
}
|
|
209
|
+
if (usyncQuery.users.length === 0) return []
|
|
265
210
|
const results = await executeUSyncQuery(usyncQuery)
|
|
266
211
|
if (results) {
|
|
267
212
|
return results.list.filter((a) => !!a.lid).map(({ lid, id }) => ({ pn: id, lid: lid }))
|
|
268
213
|
}
|
|
269
214
|
return []
|
|
270
215
|
}
|
|
271
|
-
|
|
272
|
-
const { creds } = authState
|
|
273
|
-
// add transaction capability
|
|
274
|
-
const keys = addTransactionCapability(authState.keys, logger, transactionOpts)
|
|
275
|
-
const signalRepository = makeSignalRepository({ creds, keys }, logger, pnFromLIDUSync)
|
|
276
|
-
let lastDateRecv
|
|
277
|
-
let epoch = 1
|
|
278
|
-
let preKeyMonitorInterval = null
|
|
279
|
-
let lastPreKeyCheck = 0
|
|
280
|
-
let isUploadingPreKeys = false
|
|
281
|
-
const PREKEY_CHECK_INTERVAL = 2 * 60 * 60 * 1000 // Check every 2 hours
|
|
282
|
-
const PREKEY_MIN_INTERVAL = 30 * 60 * 1000 // Minimum 30 mins between uploads
|
|
283
|
-
let keepAliveReq
|
|
284
|
-
let qrTimer
|
|
285
|
-
let closed = false
|
|
286
|
-
/** log & process any unexpected errors */
|
|
216
|
+
|
|
287
217
|
const onUnexpectedError = (err, msg) => {
|
|
288
218
|
logger.error({ err }, `unexpected error in '${msg}'`)
|
|
219
|
+
const message = (err && ((err.stack || err.message) || String(err))).toLowerCase()
|
|
220
|
+
|
|
221
|
+
// Trigger critical pre-key check on crypto errors
|
|
222
|
+
if (message.includes('bad mac') || (message.includes('mac') && message.includes('invalid'))) {
|
|
223
|
+
triggerPreKeyCheck("bad-mac", "critical")
|
|
224
|
+
}
|
|
225
|
+
if (message.includes('session') && message.includes('corrupt')) {
|
|
226
|
+
triggerPreKeyCheck("session-corruption", "critical")
|
|
227
|
+
}
|
|
289
228
|
}
|
|
290
|
-
|
|
229
|
+
|
|
291
230
|
const awaitNextMessage = async (sendMsg) => {
|
|
292
231
|
if (!ws.isOpen) {
|
|
293
|
-
throw new Boom("Connection Closed", {
|
|
294
|
-
statusCode: DisconnectReason.connectionClosed,
|
|
295
|
-
})
|
|
232
|
+
throw new Boom("Connection Closed", { statusCode: DisconnectReason.connectionClosed })
|
|
296
233
|
}
|
|
297
|
-
let onOpen
|
|
298
|
-
let onClose
|
|
234
|
+
let onOpen, onClose
|
|
299
235
|
const result = promiseTimeout(connectTimeoutMs, (resolve, reject) => {
|
|
300
236
|
onOpen = resolve
|
|
301
237
|
onClose = mapWebSocketError(reject)
|
|
@@ -312,11 +248,9 @@ export const makeSocket = (config) => {
|
|
|
312
248
|
}
|
|
313
249
|
return result
|
|
314
250
|
}
|
|
315
|
-
|
|
251
|
+
|
|
316
252
|
const validateConnection = async () => {
|
|
317
|
-
let helloMsg = {
|
|
318
|
-
clientHello: { ephemeral: ephemeralKeyPair.public },
|
|
319
|
-
}
|
|
253
|
+
let helloMsg = { clientHello: { ephemeral: ephemeralKeyPair.public } }
|
|
320
254
|
helloMsg = proto.HandshakeMessage.fromObject(helloMsg)
|
|
321
255
|
logger.info({ browser, helloMsg }, "connected to WA")
|
|
322
256
|
const init = proto.HandshakeMessage.encode(helloMsg).finish()
|
|
@@ -333,67 +267,52 @@ export const makeSocket = (config) => {
|
|
|
333
267
|
logger.info({ node }, "logging in...")
|
|
334
268
|
}
|
|
335
269
|
const payloadEnc = noise.encrypt(proto.ClientPayload.encode(node).finish())
|
|
336
|
-
await sendRawMessage(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
static: keyEnc,
|
|
340
|
-
payload: payloadEnc,
|
|
341
|
-
},
|
|
342
|
-
}).finish(),
|
|
343
|
-
)
|
|
270
|
+
await sendRawMessage(proto.HandshakeMessage.encode({
|
|
271
|
+
clientFinish: { static: keyEnc, payload: payloadEnc }
|
|
272
|
+
}).finish())
|
|
344
273
|
noise.finishInit()
|
|
345
274
|
startKeepAliveRequest()
|
|
346
275
|
}
|
|
276
|
+
|
|
347
277
|
const getAvailablePreKeysOnServer = async () => {
|
|
348
278
|
const result = await query({
|
|
349
279
|
tag: "iq",
|
|
350
|
-
attrs: {
|
|
351
|
-
|
|
352
|
-
xmlns: "encrypt",
|
|
353
|
-
type: "get",
|
|
354
|
-
to: S_WHATSAPP_NET,
|
|
355
|
-
},
|
|
356
|
-
content: [{ tag: "count", attrs: {} }],
|
|
280
|
+
attrs: { id: generateMessageTag(), xmlns: "encrypt", type: "get", to: S_WHATSAPP_NET },
|
|
281
|
+
content: [{ tag: "count", attrs: {} }]
|
|
357
282
|
})
|
|
358
283
|
const countChild = getBinaryNodeChild(result, "count")
|
|
359
284
|
return +countChild.attrs.value
|
|
360
285
|
}
|
|
361
|
-
|
|
362
|
-
let uploadPreKeysPromise = null
|
|
363
|
-
let lastUploadTime = 0
|
|
364
|
-
/** generates and uploads a set of pre-keys to the server */
|
|
286
|
+
|
|
365
287
|
const uploadPreKeys = async (count = MIN_PREKEY_COUNT, retryCount = 0) => {
|
|
366
|
-
// Check minimum interval (except for retries)
|
|
367
288
|
if (retryCount === 0) {
|
|
368
289
|
const timeSinceLastUpload = Date.now() - lastUploadTime
|
|
369
290
|
if (timeSinceLastUpload < MIN_UPLOAD_INTERVAL) {
|
|
370
|
-
logger.debug(`Skipping upload, only ${timeSinceLastUpload}ms since last
|
|
291
|
+
logger.debug(`Skipping upload, only ${timeSinceLastUpload}ms since last`)
|
|
371
292
|
return
|
|
372
293
|
}
|
|
373
294
|
}
|
|
374
|
-
// Prevent multiple concurrent uploads
|
|
375
295
|
if (uploadPreKeysPromise) {
|
|
376
|
-
logger.debug("Pre-key upload
|
|
296
|
+
logger.debug("Pre-key upload in progress, waiting")
|
|
377
297
|
await uploadPreKeysPromise
|
|
298
|
+
return
|
|
378
299
|
}
|
|
300
|
+
|
|
379
301
|
const uploadLogic = async () => {
|
|
380
302
|
logger.info({ count, retryCount }, "uploading pre-keys")
|
|
381
|
-
// Generate and save pre-keys atomically (prevents ID collisions on retry)
|
|
382
303
|
const node = await keys.transaction(async () => {
|
|
383
|
-
logger.debug({ requestedCount: count }, "generating pre-keys
|
|
304
|
+
logger.debug({ requestedCount: count }, "generating pre-keys")
|
|
384
305
|
const { update, node } = await getNextPreKeysNode({ creds, keys }, count)
|
|
385
|
-
// Update credentials immediately to prevent duplicate IDs on retry
|
|
386
306
|
ev.emit("creds.update", update)
|
|
387
|
-
return node
|
|
307
|
+
return node
|
|
388
308
|
}, creds?.me?.id || "upload-pre-keys")
|
|
389
|
-
|
|
309
|
+
|
|
390
310
|
try {
|
|
391
311
|
await query(node)
|
|
392
312
|
logger.info({ count }, "uploaded pre-keys successfully")
|
|
393
313
|
lastUploadTime = Date.now()
|
|
394
314
|
} catch (uploadError) {
|
|
395
|
-
logger.error({ uploadError: uploadError.toString(), count }, "Failed to upload pre-keys
|
|
396
|
-
// Exponential backoff retry (max 3 retries)
|
|
315
|
+
logger.error({ uploadError: uploadError.toString(), count }, "Failed to upload pre-keys")
|
|
397
316
|
if (retryCount < 3) {
|
|
398
317
|
const backoffDelay = Math.min(1000 * Math.pow(2, retryCount), 10000)
|
|
399
318
|
logger.info(`Retrying pre-key upload in ${backoffDelay}ms`)
|
|
@@ -403,93 +322,127 @@ export const makeSocket = (config) => {
|
|
|
403
322
|
throw uploadError
|
|
404
323
|
}
|
|
405
324
|
}
|
|
406
|
-
|
|
325
|
+
|
|
407
326
|
uploadPreKeysPromise = Promise.race([
|
|
408
327
|
uploadLogic(),
|
|
409
328
|
new Promise((_, reject) =>
|
|
410
|
-
setTimeout(() => reject(new Boom("Pre-key upload timeout", { statusCode: 408 })), UPLOAD_TIMEOUT)
|
|
411
|
-
)
|
|
329
|
+
setTimeout(() => reject(new Boom("Pre-key upload timeout", { statusCode: 408 })), UPLOAD_TIMEOUT)
|
|
330
|
+
)
|
|
412
331
|
])
|
|
332
|
+
|
|
413
333
|
try {
|
|
414
334
|
await uploadPreKeysPromise
|
|
415
335
|
} finally {
|
|
416
336
|
uploadPreKeysPromise = null
|
|
417
337
|
}
|
|
418
338
|
}
|
|
419
|
-
|
|
420
|
-
const smartPreKeyMonitor = async (reason = "scheduled") => {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
// Rate limiting - prevent spam
|
|
425
|
-
if (timeSinceLastCheck < PREKEY_MIN_INTERVAL && reason !== "critical") {
|
|
426
|
-
logger.debug({
|
|
427
|
-
timeSinceLastCheck,
|
|
428
|
-
reason
|
|
429
|
-
}, "Skipping pre-key check - too recent")
|
|
430
|
-
return
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Prevent concurrent uploads
|
|
434
|
-
if (isUploadingPreKeys) {
|
|
435
|
-
logger.debug({ reason }, "Pre-key upload already in progress")
|
|
436
|
-
return
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
lastPreKeyCheck = now
|
|
440
|
-
|
|
441
|
-
try {
|
|
442
|
-
logger.debug({ reason }, "Checking pre-key status")
|
|
443
|
-
const preKeyCount = await getAvailablePreKeysOnServer()
|
|
339
|
+
|
|
340
|
+
const smartPreKeyMonitor = async (reason = "scheduled", priority = "normal") => {
|
|
341
|
+
const now = Date.now()
|
|
342
|
+
const timeSinceLastCheck = now - lastPreKeyCheck
|
|
444
343
|
|
|
445
|
-
|
|
344
|
+
if (priority !== "critical") {
|
|
345
|
+
if (timeSinceLastCheck < PREKEY_MIN_INTERVAL) {
|
|
346
|
+
logger.debug({ timeSinceLastCheck, reason }, "Skipping pre-key check - too recent")
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
}
|
|
446
350
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
351
|
+
if (isUploadingPreKeys) {
|
|
352
|
+
logger.debug({ reason, priority }, "Pre-key upload in progress")
|
|
353
|
+
if (priority === "critical") {
|
|
354
|
+
preKeyCheckQueue.push({ reason, priority, timestamp: now })
|
|
355
|
+
logger.info("Critical pre-key check queued")
|
|
356
|
+
}
|
|
357
|
+
return
|
|
358
|
+
}
|
|
450
359
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
logger.info({ preKeyCount }, "
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
360
|
+
lastPreKeyCheck = now
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
logger.debug({ reason, priority }, "Checking pre-key status")
|
|
364
|
+
const preKeyCount = await getAvailablePreKeysOnServer()
|
|
365
|
+
logger.info({ preKeyCount, reason, priority }, "Pre-key check result")
|
|
366
|
+
|
|
367
|
+
let shouldUpload = false, uploadCount = 0
|
|
368
|
+
|
|
369
|
+
if (preKeyCount <= PREKEY_CRITICAL_THRESHOLD) {
|
|
370
|
+
logger.warn({ preKeyCount }, "🚨 CRITICAL: Very low pre-keys!")
|
|
371
|
+
shouldUpload = true
|
|
372
|
+
uploadCount = INITIAL_PREKEY_COUNT
|
|
373
|
+
priority = "critical"
|
|
374
|
+
} else if (preKeyCount < MIN_PREKEY_COUNT) {
|
|
375
|
+
logger.info({ preKeyCount }, "⚠️ Low pre-keys detected")
|
|
376
|
+
shouldUpload = true
|
|
377
|
+
uploadCount = Math.max(10, MIN_PREKEY_COUNT - preKeyCount + 5)
|
|
378
|
+
} else if (priority === "critical") {
|
|
379
|
+
logger.info({ preKeyCount }, "Uploading pre-keys for critical recovery")
|
|
380
|
+
shouldUpload = true
|
|
381
|
+
uploadCount = MIN_PREKEY_COUNT
|
|
382
|
+
} else {
|
|
383
|
+
logger.debug({ preKeyCount }, "✅ Pre-key count healthy")
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (shouldUpload) {
|
|
387
|
+
isUploadingPreKeys = true
|
|
388
|
+
await uploadPreKeys(uploadCount)
|
|
389
|
+
if (preKeyCheckQueue.length > 0) {
|
|
390
|
+
logger.info(`Processing ${preKeyCheckQueue.length} queued checks`)
|
|
391
|
+
preKeyCheckQueue = []
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
} catch (error) {
|
|
395
|
+
logger.error({ error, reason, priority }, "Pre-key check failed")
|
|
396
|
+
if (priority === "critical") {
|
|
397
|
+
setTimeout(() => {
|
|
398
|
+
smartPreKeyMonitor(reason, "critical").catch(err =>
|
|
399
|
+
logger.error({ err }, "Critical pre-key retry failed")
|
|
400
|
+
)
|
|
401
|
+
}, 10000)
|
|
402
|
+
}
|
|
403
|
+
} finally {
|
|
404
|
+
isUploadingPreKeys = false
|
|
461
405
|
}
|
|
462
|
-
} catch (error) {
|
|
463
|
-
logger.error({ error, reason }, "Pre-key check failed")
|
|
464
|
-
} finally {
|
|
465
|
-
isUploadingPreKeys = false
|
|
466
406
|
}
|
|
467
|
-
}
|
|
468
407
|
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
408
|
+
const triggerPreKeyCheck = (event, priority = "normal") => {
|
|
409
|
+
const triggers = {
|
|
410
|
+
"signal-error": "critical", "bad-mac": "critical", "session-corruption": "critical",
|
|
411
|
+
"auth-failure": "critical", "connection-established": "high", "connection-restored": "high",
|
|
412
|
+
"device-paired": "high", "message-send-error": "normal", "message-received": "normal",
|
|
413
|
+
"scheduled": "low", "keep-alive": "low"
|
|
414
|
+
}
|
|
415
|
+
const effectivePriority = triggers[event] || priority
|
|
416
|
+
logger.debug({ event, priority: effectivePriority }, "Pre-key check triggered")
|
|
417
|
+
smartPreKeyMonitor(event, effectivePriority).catch(err => {
|
|
418
|
+
logger.error({ err, event }, "Triggered pre-key check failed")
|
|
478
419
|
})
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const startPreKeyBackgroundMonitor = () => {
|
|
423
|
+
if (preKeyMonitorInterval) clearInterval(preKeyMonitorInterval)
|
|
424
|
+
preKeyMonitorInterval = setInterval(() => {
|
|
425
|
+
triggerPreKeyCheck("scheduled", "low")
|
|
426
|
+
}, PREKEY_CHECK_INTERVAL)
|
|
427
|
+
logger.info({ intervalMinutes: PREKEY_CHECK_INTERVAL / (60 * 1000) }, "Started pre-key monitor")
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const stopPreKeyBackgroundMonitor = () => {
|
|
431
|
+
if (preKeyMonitorInterval) {
|
|
432
|
+
clearInterval(preKeyMonitorInterval)
|
|
433
|
+
preKeyMonitorInterval = null
|
|
434
|
+
logger.debug("Stopped pre-key monitor")
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
484
438
|
const verifyCurrentPreKeyExists = async () => {
|
|
485
439
|
const currentPreKeyId = creds.nextPreKeyId - 1
|
|
486
|
-
if (currentPreKeyId <= 0) {
|
|
487
|
-
return { exists: false, currentPreKeyId: 0 }
|
|
488
|
-
}
|
|
440
|
+
if (currentPreKeyId <= 0) return { exists: false, currentPreKeyId: 0 }
|
|
489
441
|
const preKeys = await keys.get("pre-key", [currentPreKeyId.toString()])
|
|
490
442
|
const exists = !!preKeys[currentPreKeyId.toString()]
|
|
491
443
|
return { exists, currentPreKeyId }
|
|
492
444
|
}
|
|
445
|
+
|
|
493
446
|
const uploadPreKeysToServerIfRequired = async () => {
|
|
494
447
|
try {
|
|
495
448
|
let count = 0
|
|
@@ -498,42 +451,37 @@ const startPreKeyBackgroundMonitor = () => {
|
|
|
498
451
|
else count = MIN_PREKEY_COUNT
|
|
499
452
|
const { exists: currentPreKeyExists, currentPreKeyId } = await verifyCurrentPreKeyExists()
|
|
500
453
|
logger.info(`${preKeyCount} pre-keys found on server`)
|
|
501
|
-
logger.info(`Current prekey ID: ${currentPreKeyId}, exists
|
|
454
|
+
logger.info(`Current prekey ID: ${currentPreKeyId}, exists: ${currentPreKeyExists}`)
|
|
502
455
|
const lowServerCount = preKeyCount <= count
|
|
503
456
|
const missingCurrentPreKey = !currentPreKeyExists && currentPreKeyId > 0
|
|
504
457
|
const shouldUpload = lowServerCount || missingCurrentPreKey
|
|
505
458
|
if (shouldUpload) {
|
|
506
459
|
const reasons = []
|
|
507
460
|
if (lowServerCount) reasons.push(`server count low (${preKeyCount})`)
|
|
508
|
-
if (missingCurrentPreKey) reasons.push(`current prekey ${currentPreKeyId} missing
|
|
461
|
+
if (missingCurrentPreKey) reasons.push(`current prekey ${currentPreKeyId} missing`)
|
|
509
462
|
logger.info(`Uploading PreKeys due to: ${reasons.join(", ")}`)
|
|
510
463
|
await uploadPreKeys(count)
|
|
511
464
|
} else {
|
|
512
|
-
logger.info(`PreKey validation passed - Server: ${preKeyCount}, Current
|
|
465
|
+
logger.info(`PreKey validation passed - Server: ${preKeyCount}, Current ${currentPreKeyId} exists`)
|
|
513
466
|
}
|
|
514
467
|
} catch (error) {
|
|
515
|
-
logger.error({ error }, "Failed to check/upload pre-keys during
|
|
516
|
-
// Don't throw - allow connection to continue even if pre-key check fails
|
|
468
|
+
logger.error({ error }, "Failed to check/upload pre-keys during init")
|
|
517
469
|
}
|
|
518
470
|
}
|
|
471
|
+
|
|
519
472
|
const onMessageReceived = (data) => {
|
|
520
473
|
noise.decodeFrame(data, (frame) => {
|
|
521
|
-
// reset ping timeout
|
|
522
474
|
lastDateRecv = new Date()
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
anyTriggered = ws.emit("frame", frame)
|
|
526
|
-
// if it's a binary node
|
|
475
|
+
lastMessageTime = Date.now()
|
|
476
|
+
reconnectAttempts = 0 // Reset on successful message
|
|
477
|
+
let anyTriggered = ws.emit("frame", frame)
|
|
527
478
|
if (!(frame instanceof Uint8Array)) {
|
|
528
479
|
const msgId = frame.attrs.id
|
|
529
480
|
if (logger.level === "trace") {
|
|
530
481
|
logger.trace({ xml: binaryNodeToString(frame), msg: "recv xml" })
|
|
531
482
|
}
|
|
532
|
-
/* Check if this is a response to a message we sent */
|
|
533
483
|
anyTriggered = ws.emit(`${DEF_TAG_PREFIX}${msgId}`, frame) || anyTriggered
|
|
534
|
-
|
|
535
|
-
const l0 = frame.tag
|
|
536
|
-
const l1 = frame.attrs || {}
|
|
484
|
+
const l0 = frame.tag, l1 = frame.attrs || {}
|
|
537
485
|
const l2 = Array.isArray(frame.content) ? frame.content[0]?.tag : ""
|
|
538
486
|
for (const key of Object.keys(l1)) {
|
|
539
487
|
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]},${l2}`, frame) || anyTriggered
|
|
@@ -548,48 +496,52 @@ const startPreKeyBackgroundMonitor = () => {
|
|
|
548
496
|
}
|
|
549
497
|
})
|
|
550
498
|
}
|
|
499
|
+
|
|
551
500
|
const end = (error) => {
|
|
552
501
|
if (closed) {
|
|
553
502
|
logger.trace({ trace: error?.stack }, "connection already closed")
|
|
554
503
|
return
|
|
555
504
|
}
|
|
556
505
|
closed = true
|
|
557
|
-
|
|
506
|
+
|
|
507
|
+
// Only log and end if it's a real error, not just Connection Terminated
|
|
508
|
+
const shouldLogError = error && error.message !== "Connection Terminated"
|
|
509
|
+
if (shouldLogError) {
|
|
510
|
+
logger.info({ trace: error?.stack }, "connection errored")
|
|
511
|
+
} else {
|
|
512
|
+
logger.debug("connection closed gracefully")
|
|
513
|
+
}
|
|
514
|
+
|
|
558
515
|
clearInterval(keepAliveReq)
|
|
559
|
-
clearInterval(sessionHealthCheck)
|
|
516
|
+
clearInterval(sessionHealthCheck)
|
|
560
517
|
clearTimeout(qrTimer)
|
|
518
|
+
stopPreKeyBackgroundMonitor()
|
|
519
|
+
|
|
561
520
|
ws.removeAllListeners("close")
|
|
562
521
|
ws.removeAllListeners("open")
|
|
563
522
|
ws.removeAllListeners("message")
|
|
564
|
-
|
|
565
|
-
if (preKeyMonitorInterval) {
|
|
566
|
-
clearInterval(preKeyMonitorInterval)
|
|
567
|
-
preKeyMonitorInterval = null
|
|
568
|
-
logger.debug("Stopped pre-key background monitor")
|
|
569
|
-
}
|
|
523
|
+
|
|
570
524
|
if (!ws.isClosed && !ws.isClosing) {
|
|
571
|
-
try {
|
|
572
|
-
ws.close()
|
|
573
|
-
} catch {}
|
|
525
|
+
try { ws.close() } catch {}
|
|
574
526
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
527
|
+
|
|
528
|
+
// Don't emit if it's just a normal close
|
|
529
|
+
if (shouldLogError || (error && error.output?.statusCode !== DisconnectReason.connectionClosed)) {
|
|
530
|
+
ev.emit("connection.update", {
|
|
531
|
+
connection: "close",
|
|
532
|
+
lastDisconnect: { error, date: new Date() }
|
|
533
|
+
})
|
|
534
|
+
}
|
|
535
|
+
|
|
582
536
|
ev.removeAllListeners("connection.update")
|
|
583
537
|
}
|
|
538
|
+
|
|
584
539
|
const waitForSocketOpen = async () => {
|
|
585
|
-
if (ws.isOpen)
|
|
586
|
-
return
|
|
587
|
-
}
|
|
540
|
+
if (ws.isOpen) return
|
|
588
541
|
if (ws.isClosed || ws.isClosing) {
|
|
589
542
|
throw new Boom("Connection Closed", { statusCode: DisconnectReason.connectionClosed })
|
|
590
543
|
}
|
|
591
|
-
let onOpen
|
|
592
|
-
let onClose
|
|
544
|
+
let onOpen, onClose
|
|
593
545
|
await new Promise((resolve, reject) => {
|
|
594
546
|
onOpen = () => resolve(undefined)
|
|
595
547
|
onClose = mapWebSocketError(reject)
|
|
@@ -602,180 +554,114 @@ const startPreKeyBackgroundMonitor = () => {
|
|
|
602
554
|
ws.off("error", onClose)
|
|
603
555
|
})
|
|
604
556
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
} else if (ws.isOpen) {
|
|
618
|
-
// if its all good, send a keep alive request
|
|
619
|
-
query({
|
|
557
|
+
|
|
558
|
+
const startKeepAliveRequest = () => {
|
|
559
|
+
let consecutiveFailedPings = 0
|
|
560
|
+
const MAX_FAILED_PINGS = 3 // Allow 3 failed pings before giving up
|
|
561
|
+
|
|
562
|
+
keepAliveReq = setInterval(async () => {
|
|
563
|
+
if (!lastDateRecv) lastDateRecv = new Date()
|
|
564
|
+
|
|
565
|
+
if (ws.isOpen) {
|
|
566
|
+
try {
|
|
567
|
+
// Send ping and WAIT for response
|
|
568
|
+
await query({
|
|
620
569
|
tag: "iq",
|
|
621
|
-
attrs: {
|
|
622
|
-
|
|
623
|
-
to: S_WHATSAPP_NET,
|
|
624
|
-
type: "get",
|
|
625
|
-
xmlns: "w:p",
|
|
626
|
-
},
|
|
627
|
-
content: [{ tag: "ping", attrs: {} }],
|
|
628
|
-
}).catch((err) => {
|
|
629
|
-
logger.error({ trace: err.stack }, "error in sending keep alive")
|
|
570
|
+
attrs: { id: generateMessageTag(), to: S_WHATSAPP_NET, type: "get", xmlns: "w:p" },
|
|
571
|
+
content: [{ tag: "ping", attrs: {} }]
|
|
630
572
|
})
|
|
631
|
-
|
|
632
|
-
|
|
573
|
+
|
|
574
|
+
// ✅ Ping succeeded - reset failure counter
|
|
575
|
+
consecutiveFailedPings = 0
|
|
576
|
+
logger.debug("Keep-alive ping successful")
|
|
577
|
+
|
|
578
|
+
} catch (err) {
|
|
579
|
+
// ❌ Ping failed
|
|
580
|
+
consecutiveFailedPings++
|
|
581
|
+
logger.warn({
|
|
582
|
+
consecutiveFailures: consecutiveFailedPings,
|
|
583
|
+
maxAllowed: MAX_FAILED_PINGS
|
|
584
|
+
}, "Keep-alive ping failed")
|
|
585
|
+
|
|
586
|
+
// Only kill connection after multiple consecutive failures
|
|
587
|
+
if (consecutiveFailedPings >= MAX_FAILED_PINGS) {
|
|
588
|
+
logger.error("Multiple consecutive ping failures - connection lost")
|
|
589
|
+
end(new Boom("Connection was lost", { statusCode: DisconnectReason.connectionLost }))
|
|
590
|
+
}
|
|
633
591
|
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
592
|
+
} else {
|
|
593
|
+
logger.warn("keep alive called when WS not open")
|
|
594
|
+
}
|
|
595
|
+
}, keepAliveIntervalMs)
|
|
596
|
+
}
|
|
639
597
|
|
|
640
598
|
const startSessionHealthMonitor = () => {
|
|
641
599
|
sessionHealthCheck = setInterval(() => {
|
|
642
600
|
const timeSinceLastMsg = Date.now() - lastMessageTime
|
|
643
|
-
|
|
644
|
-
const healthCheckIntervalMs = keepAliveIntervalMs * 10 // 5 minutes with default 30s keep-alive
|
|
645
|
-
|
|
646
|
-
// Only log warning, don't force disconnect - let keepalive handle it
|
|
601
|
+
const healthCheckIntervalMs = keepAliveIntervalMs * 10
|
|
647
602
|
if (timeSinceLastMsg > healthCheckIntervalMs && ws.isOpen) {
|
|
648
|
-
logger.warn(
|
|
649
|
-
{ timeSinceLastMsg, threshold: healthCheckIntervalMs },
|
|
650
|
-
"Session health check: extended inactivity detected",
|
|
651
|
-
)
|
|
652
|
-
// The keepalive mechanism will naturally detect and handle dead connections
|
|
603
|
+
logger.warn({ timeSinceLastMsg, threshold: healthCheckIntervalMs }, "Extended inactivity detected")
|
|
653
604
|
}
|
|
654
|
-
}, keepAliveIntervalMs * 4)
|
|
605
|
+
}, keepAliveIntervalMs * 4)
|
|
655
606
|
}
|
|
656
607
|
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
tag: "iq",
|
|
664
|
-
attrs: {
|
|
665
|
-
to: S_WHATSAPP_NET,
|
|
666
|
-
xmlns: "passive",
|
|
667
|
-
type: "set",
|
|
668
|
-
},
|
|
669
|
-
content: [{ tag, attrs: {} }],
|
|
670
|
-
})
|
|
671
|
-
/** logout & invalidate connection */
|
|
608
|
+
const sendPassiveIq = (tag) => query({
|
|
609
|
+
tag: "iq",
|
|
610
|
+
attrs: { to: S_WHATSAPP_NET, xmlns: "passive", type: "set" },
|
|
611
|
+
content: [{ tag, attrs: {} }]
|
|
612
|
+
})
|
|
613
|
+
|
|
672
614
|
const logout = async (msg) => {
|
|
673
615
|
const jid = authState.creds.me?.id
|
|
674
616
|
if (jid) {
|
|
675
617
|
await sendNode({
|
|
676
618
|
tag: "iq",
|
|
677
|
-
attrs: {
|
|
678
|
-
|
|
679
|
-
type: "set",
|
|
680
|
-
id: generateMessageTag(),
|
|
681
|
-
xmlns: "md",
|
|
682
|
-
},
|
|
683
|
-
content: [
|
|
684
|
-
{
|
|
685
|
-
tag: "remove-companion-device",
|
|
686
|
-
attrs: {
|
|
687
|
-
jid,
|
|
688
|
-
reason: "user_initiated",
|
|
689
|
-
},
|
|
690
|
-
},
|
|
691
|
-
],
|
|
619
|
+
attrs: { to: S_WHATSAPP_NET, type: "set", id: generateMessageTag(), xmlns: "md" },
|
|
620
|
+
content: [{ tag: "remove-companion-device", attrs: { jid, reason: "user_initiated" } }]
|
|
692
621
|
})
|
|
693
622
|
}
|
|
694
623
|
end(new Boom(msg || "Intentional Logout", { statusCode: DisconnectReason.loggedOut }))
|
|
695
624
|
}
|
|
625
|
+
|
|
696
626
|
const requestPairingCode = async (phoneNumber, customPairingCode) => {
|
|
697
627
|
const pairingCode = customPairingCode ?? bytesToCrockford(randomBytes(5))
|
|
698
628
|
if (customPairingCode && customPairingCode?.length !== 8) {
|
|
699
629
|
throw new Error("Custom pairing code must be exactly 8 chars")
|
|
700
630
|
}
|
|
701
631
|
authState.creds.pairingCode = pairingCode
|
|
702
|
-
authState.creds.me = {
|
|
703
|
-
id: jidEncode(phoneNumber, "s.whatsapp.net"),
|
|
704
|
-
name: "~",
|
|
705
|
-
}
|
|
632
|
+
authState.creds.me = { id: jidEncode(phoneNumber, "s.whatsapp.net"), name: "~" }
|
|
706
633
|
ev.emit("creds.update", authState.creds)
|
|
707
634
|
await sendNode({
|
|
708
635
|
tag: "iq",
|
|
709
|
-
attrs: {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
id:
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
tag: "
|
|
718
|
-
attrs: {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
should_show_push_notification: "true",
|
|
722
|
-
},
|
|
723
|
-
content: [
|
|
724
|
-
{
|
|
725
|
-
tag: "link_code_pairing_wrapped_companion_ephemeral_pub",
|
|
726
|
-
attrs: {},
|
|
727
|
-
content: await generatePairingKey(),
|
|
728
|
-
},
|
|
729
|
-
{
|
|
730
|
-
tag: "companion_server_auth_key_pub",
|
|
731
|
-
attrs: {},
|
|
732
|
-
content: authState.creds.noiseKey.public,
|
|
733
|
-
},
|
|
734
|
-
{
|
|
735
|
-
tag: "companion_platform_id",
|
|
736
|
-
attrs: {},
|
|
737
|
-
content: getPlatformId(browser[1]),
|
|
738
|
-
},
|
|
739
|
-
{
|
|
740
|
-
tag: "companion_platform_display",
|
|
741
|
-
attrs: {},
|
|
742
|
-
content: `${browser[1]} (${browser[0]})`,
|
|
743
|
-
},
|
|
744
|
-
{
|
|
745
|
-
tag: "link_code_pairing_nonce",
|
|
746
|
-
attrs: {},
|
|
747
|
-
content: "0",
|
|
748
|
-
},
|
|
749
|
-
],
|
|
750
|
-
},
|
|
751
|
-
],
|
|
636
|
+
attrs: { to: S_WHATSAPP_NET, type: "set", id: generateMessageTag(), xmlns: "md" },
|
|
637
|
+
content: [{
|
|
638
|
+
tag: "link_code_companion_reg",
|
|
639
|
+
attrs: { jid: authState.creds.me.id, stage: "companion_hello", should_show_push_notification: "true" },
|
|
640
|
+
content: [
|
|
641
|
+
{ tag: "link_code_pairing_wrapped_companion_ephemeral_pub", attrs: {}, content: await generatePairingKey() },
|
|
642
|
+
{ tag: "companion_server_auth_key_pub", attrs: {}, content: authState.creds.noiseKey.public },
|
|
643
|
+
{ tag: "companion_platform_id", attrs: {}, content: getPlatformId(browser[1]) },
|
|
644
|
+
{ tag: "companion_platform_display", attrs: {}, content: `${browser[1]} (${browser[0]})` },
|
|
645
|
+
{ tag: "link_code_pairing_nonce", attrs: {}, content: "0" }
|
|
646
|
+
]
|
|
647
|
+
}]
|
|
752
648
|
})
|
|
753
649
|
return authState.creds.pairingCode
|
|
754
650
|
}
|
|
651
|
+
|
|
755
652
|
async function generatePairingKey() {
|
|
756
|
-
const salt = randomBytes(32)
|
|
757
|
-
const randomIv = randomBytes(16)
|
|
653
|
+
const salt = randomBytes(32), randomIv = randomBytes(16)
|
|
758
654
|
const key = await derivePairingCodeKey(authState.creds.pairingCode, salt)
|
|
759
655
|
const ciphered = aesEncryptCTR(authState.creds.pairingEphemeralKeyPair.public, key, randomIv)
|
|
760
656
|
return Buffer.concat([salt, randomIv, ciphered])
|
|
761
657
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
},
|
|
770
|
-
content: [
|
|
771
|
-
{
|
|
772
|
-
tag: "add",
|
|
773
|
-
attrs: { t: Math.round(Date.now() / 1000) + "" },
|
|
774
|
-
content: wamBuffer,
|
|
775
|
-
},
|
|
776
|
-
],
|
|
777
|
-
})
|
|
778
|
-
}
|
|
658
|
+
|
|
659
|
+
const sendWAMBuffer = (wamBuffer) => query({
|
|
660
|
+
tag: "iq",
|
|
661
|
+
attrs: { to: S_WHATSAPP_NET, id: generateMessageTag(), xmlns: "w:stats" },
|
|
662
|
+
content: [{ tag: "add", attrs: { t: Math.round(Date.now() / 1000) + "" }, content: wamBuffer }]
|
|
663
|
+
})
|
|
664
|
+
|
|
779
665
|
ws.on("message", onMessageReceived)
|
|
780
666
|
ws.on("open", async () => {
|
|
781
667
|
try {
|
|
@@ -785,33 +671,46 @@ const startPreKeyBackgroundMonitor = () => {
|
|
|
785
671
|
end(err)
|
|
786
672
|
}
|
|
787
673
|
})
|
|
788
|
-
|
|
789
|
-
ws.on("
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
674
|
+
|
|
675
|
+
ws.on("error", (err) => {
|
|
676
|
+
logger.warn({ err: err.message }, "WebSocket error occurred")
|
|
677
|
+
// Don't immediately end - let other mechanisms handle it
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
ws.on("close", (code, reason) => {
|
|
681
|
+
logger.debug({ code, reason: reason?.toString() }, "WebSocket closed")
|
|
682
|
+
// Graceful close, don't throw error
|
|
683
|
+
if (!closed) {
|
|
684
|
+
setTimeout(() => {
|
|
685
|
+
if (!closed && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
|
686
|
+
reconnectAttempts++
|
|
687
|
+
logger.info({ attempt: reconnectAttempts }, "Attempting to reconnect...")
|
|
688
|
+
ws.restart().catch(err => logger.error({ err }, "Reconnect failed"))
|
|
689
|
+
} else if (!closed) {
|
|
690
|
+
end(new Boom("Connection Terminated", { statusCode: DisconnectReason.connectionClosed }))
|
|
691
|
+
}
|
|
692
|
+
}, Math.min(1000 * Math.pow(2, reconnectAttempts), 10000)) // Exponential backoff
|
|
693
|
+
}
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
ws.on("CB:xmlstreamend", () => {
|
|
697
|
+
logger.info("Stream ended by server")
|
|
698
|
+
if (!closed) {
|
|
699
|
+
end(new Boom("Connection Terminated by Server", { statusCode: DisconnectReason.connectionClosed }))
|
|
803
700
|
}
|
|
701
|
+
})
|
|
702
|
+
|
|
703
|
+
ws.on("CB:iq,type:set,pair-device", async (stanza) => {
|
|
704
|
+
const iq = { tag: "iq", attrs: { to: S_WHATSAPP_NET, type: "result", id: stanza.attrs.id } }
|
|
804
705
|
await sendNode(iq)
|
|
805
706
|
const pairDeviceNode = getBinaryNodeChild(stanza, "pair-device")
|
|
806
707
|
const refNodes = getBinaryNodeChildren(pairDeviceNode, "ref")
|
|
807
708
|
const noiseKeyB64 = Buffer.from(creds.noiseKey.public).toString("base64")
|
|
808
709
|
const identityKeyB64 = Buffer.from(creds.signedIdentityKey.public).toString("base64")
|
|
809
710
|
const advB64 = creds.advSecretKey
|
|
810
|
-
let qrMs = qrTimeout || 60000
|
|
711
|
+
let qrMs = qrTimeout || 60000
|
|
811
712
|
const genPairQR = () => {
|
|
812
|
-
if (!ws.isOpen)
|
|
813
|
-
return
|
|
814
|
-
}
|
|
713
|
+
if (!ws.isOpen) return
|
|
815
714
|
const refNode = refNodes.shift()
|
|
816
715
|
if (!refNode) {
|
|
817
716
|
end(new Boom("QR refs attempts ended", { statusCode: DisconnectReason.timedOut }))
|
|
@@ -821,29 +720,26 @@ const startPreKeyBackgroundMonitor = () => {
|
|
|
821
720
|
const qr = [ref, noiseKeyB64, identityKeyB64, advB64].join(",")
|
|
822
721
|
ev.emit("connection.update", { qr })
|
|
823
722
|
qrTimer = setTimeout(genPairQR, qrMs)
|
|
824
|
-
qrMs = qrTimeout || 20000
|
|
723
|
+
qrMs = qrTimeout || 20000
|
|
825
724
|
}
|
|
826
725
|
genPairQR()
|
|
827
726
|
})
|
|
828
|
-
|
|
829
|
-
// if device pairs successfully, the server asks to restart the connection
|
|
727
|
+
|
|
830
728
|
ws.on("CB:iq,,pair-success", async (stanza) => {
|
|
831
729
|
logger.debug("pair success recv")
|
|
832
730
|
try {
|
|
833
731
|
const { reply, creds: updatedCreds } = configureSuccessfulPairing(stanza, creds)
|
|
834
|
-
logger.info(
|
|
835
|
-
{ me: updatedCreds.me, platform: updatedCreds.platform },
|
|
836
|
-
"pairing configured successfully, expect to restart the connection...",
|
|
837
|
-
)
|
|
732
|
+
logger.info({ me: updatedCreds.me, platform: updatedCreds.platform }, "pairing configured successfully")
|
|
838
733
|
ev.emit("creds.update", updatedCreds)
|
|
839
734
|
ev.emit("connection.update", { isNewLogin: true, qr: undefined })
|
|
735
|
+
triggerPreKeyCheck("device-paired", "high")
|
|
840
736
|
await sendNode(reply)
|
|
841
737
|
} catch (error) {
|
|
842
738
|
logger.info({ trace: error.stack }, "error in pairing")
|
|
843
739
|
end(error)
|
|
844
740
|
}
|
|
845
741
|
})
|
|
846
|
-
|
|
742
|
+
|
|
847
743
|
ws.on("CB:success", async (node) => {
|
|
848
744
|
try {
|
|
849
745
|
await uploadPreKeysToServerIfRequired()
|
|
@@ -852,33 +748,21 @@ const startPreKeyBackgroundMonitor = () => {
|
|
|
852
748
|
logger.warn({ err }, "failed to send initial passive iq")
|
|
853
749
|
}
|
|
854
750
|
logger.info("opened connection to WA")
|
|
855
|
-
clearTimeout(qrTimer)
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
logger.warn({ err }, "Initial pre-key check failed")
|
|
859
|
-
})
|
|
860
|
-
|
|
861
|
-
// Start background monitor
|
|
862
|
-
startPreKeyBackgroundMonitor()
|
|
751
|
+
clearTimeout(qrTimer)
|
|
752
|
+
triggerPreKeyCheck("connection-established", "high")
|
|
753
|
+
startPreKeyBackgroundMonitor()
|
|
863
754
|
ev.emit("creds.update", { me: { ...authState.creds.me, lid: node.attrs.lid } })
|
|
864
755
|
ev.emit("connection.update", { connection: "open" })
|
|
865
|
-
// Start monitoring session health after successful connection
|
|
866
756
|
startSessionHealthMonitor()
|
|
757
|
+
reconnectAttempts = 0
|
|
867
758
|
if (node.attrs.lid && authState.creds.me?.id) {
|
|
868
759
|
const myLID = node.attrs.lid
|
|
869
760
|
process.nextTick(async () => {
|
|
870
761
|
try {
|
|
871
762
|
const myPN = authState.creds.me.id
|
|
872
|
-
// Store our own LID-PN mapping
|
|
873
763
|
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: myLID, pn: myPN }])
|
|
874
|
-
// Create device list for our own user (needed for bulk migration)
|
|
875
764
|
const { user, device } = jidDecode(myPN)
|
|
876
|
-
await authState.keys.set({
|
|
877
|
-
"device-list": {
|
|
878
|
-
[user]: [device?.toString() || "0"],
|
|
879
|
-
},
|
|
880
|
-
})
|
|
881
|
-
// migrate our own session
|
|
765
|
+
await authState.keys.set({ "device-list": { [user]: [device?.toString() || "0"] } })
|
|
882
766
|
await signalRepository.migrateSession(myPN, myLID)
|
|
883
767
|
logger.info({ myPN, myLID }, "Own LID session created successfully")
|
|
884
768
|
} catch (error) {
|
|
@@ -887,27 +771,27 @@ const startPreKeyBackgroundMonitor = () => {
|
|
|
887
771
|
})
|
|
888
772
|
}
|
|
889
773
|
})
|
|
774
|
+
|
|
890
775
|
ws.on("CB:stream:error", (node) => {
|
|
891
776
|
logger.error({ node }, "stream errored out")
|
|
892
777
|
const { reason, statusCode } = getErrorCodeFromStreamError(node)
|
|
893
778
|
end(new Boom(`Stream Errored (${reason})`, { statusCode, data: node }))
|
|
894
779
|
})
|
|
895
|
-
|
|
780
|
+
|
|
896
781
|
ws.on("CB:failure", (node) => {
|
|
897
782
|
const reason = +(node.attrs.reason || 500)
|
|
898
783
|
end(new Boom("Connection Failure", { statusCode: reason, data: node.attrs }))
|
|
899
784
|
})
|
|
785
|
+
|
|
900
786
|
ws.on("CB:ib,,downgrade_webclient", () => {
|
|
901
787
|
end(new Boom("Multi-device beta not joined", { statusCode: DisconnectReason.multideviceMismatch }))
|
|
902
788
|
})
|
|
789
|
+
|
|
903
790
|
ws.on("CB:ib,,offline_preview", (node) => {
|
|
904
791
|
logger.info("offline preview received", JSON.stringify(node))
|
|
905
|
-
sendNode({
|
|
906
|
-
tag: "ib",
|
|
907
|
-
attrs: {},
|
|
908
|
-
content: [{ tag: "offline_batch", attrs: { count: "100" } }],
|
|
909
|
-
})
|
|
792
|
+
sendNode({ tag: "ib", attrs: {}, content: [{ tag: "offline_batch", attrs: { count: "100" } }] })
|
|
910
793
|
})
|
|
794
|
+
|
|
911
795
|
ws.on("CB:ib,,edge_routing", (node) => {
|
|
912
796
|
const edgeRoutingNode = getBinaryNodeChild(node, "edge_routing")
|
|
913
797
|
const routingInfo = getBinaryNodeChild(edgeRoutingNode, "routing_info")
|
|
@@ -916,17 +800,16 @@ const startPreKeyBackgroundMonitor = () => {
|
|
|
916
800
|
ev.emit("creds.update", authState.creds)
|
|
917
801
|
}
|
|
918
802
|
})
|
|
803
|
+
|
|
919
804
|
let didStartBuffer = false
|
|
920
805
|
process.nextTick(() => {
|
|
921
806
|
if (creds.me?.id) {
|
|
922
|
-
// start buffering important events
|
|
923
|
-
// if we're logged in
|
|
924
807
|
ev.buffer()
|
|
925
808
|
didStartBuffer = true
|
|
926
809
|
}
|
|
927
810
|
ev.emit("connection.update", { connection: "connecting", receivedPendingNotifications: false, qr: undefined })
|
|
928
811
|
})
|
|
929
|
-
|
|
812
|
+
|
|
930
813
|
ws.on("CB:ib,,offline", (node) => {
|
|
931
814
|
const child = getBinaryNodeChild(node, "offline")
|
|
932
815
|
const offlineNotifs = +(child?.attrs.count || 0)
|
|
@@ -937,29 +820,21 @@ const startPreKeyBackgroundMonitor = () => {
|
|
|
937
820
|
}
|
|
938
821
|
ev.emit("connection.update", { receivedPendingNotifications: true })
|
|
939
822
|
})
|
|
940
|
-
|
|
823
|
+
|
|
941
824
|
ev.on("creds.update", (update) => {
|
|
942
825
|
const name = update.me?.name
|
|
943
|
-
// if name has just been received
|
|
944
826
|
if (creds.me?.name !== name) {
|
|
945
827
|
logger.debug({ name }, "updated pushName")
|
|
946
|
-
sendNode({
|
|
947
|
-
tag: "presence",
|
|
948
|
-
attrs: { name: name },
|
|
949
|
-
}).catch((err) => {
|
|
828
|
+
sendNode({ tag: "presence", attrs: { name: name } }).catch((err) => {
|
|
950
829
|
logger.warn({ trace: err.stack }, "error in sending presence update on name change")
|
|
951
830
|
})
|
|
952
831
|
}
|
|
953
832
|
Object.assign(creds, update)
|
|
954
833
|
})
|
|
955
834
|
|
|
956
|
-
/**
|
|
957
|
-
* Regenerate pre-keys immediately when Signal protocol errors occur
|
|
958
|
-
*/
|
|
959
835
|
const regeneratePreKeysForRecovery = async () => {
|
|
960
836
|
try {
|
|
961
837
|
logger.info("Initiating aggressive pre-key regeneration for signal recovery")
|
|
962
|
-
// Upload 15 fresh pre-keys immediately for recovery
|
|
963
838
|
const preKeyNodes = getNextPreKeysNode(authState, MIN_PREKEY_COUNT)
|
|
964
839
|
if (preKeyNodes) {
|
|
965
840
|
await sendNode(preKeyNodes)
|
|
@@ -970,7 +845,6 @@ const startPreKeyBackgroundMonitor = () => {
|
|
|
970
845
|
}
|
|
971
846
|
}
|
|
972
847
|
|
|
973
|
-
// Handler for signal session corruption on connection issues
|
|
974
848
|
const handleSignalSessionCorruption = async (reason) => {
|
|
975
849
|
if (reason === "SIGNAL_SESSION_CORRUPTED" || reason === "BAD_MAC_ERROR") {
|
|
976
850
|
logger.warn("Signal session corruption detected - regenerating pre-keys immediately")
|
|
@@ -979,52 +853,23 @@ const startPreKeyBackgroundMonitor = () => {
|
|
|
979
853
|
}
|
|
980
854
|
|
|
981
855
|
return {
|
|
982
|
-
type: "md",
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
},
|
|
990
|
-
generateMessageTag,
|
|
991
|
-
query,
|
|
992
|
-
waitForMessage,
|
|
993
|
-
waitForSocketOpen,
|
|
994
|
-
sendRawMessage,
|
|
995
|
-
sendNode,
|
|
996
|
-
logout,
|
|
997
|
-
end,
|
|
998
|
-
onUnexpectedError,
|
|
999
|
-
uploadPreKeys,
|
|
1000
|
-
uploadPreKeysToServerIfRequired,
|
|
1001
|
-
requestPairingCode,
|
|
1002
|
-
wamBuffer: publicWAMBuffer,
|
|
1003
|
-
/** Waits for the connection to WA to reach a state */
|
|
1004
|
-
waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),
|
|
1005
|
-
sendWAMBuffer,
|
|
1006
|
-
executeUSyncQuery,
|
|
1007
|
-
onWhatsApp,
|
|
1008
|
-
regeneratePreKeysForRecovery,
|
|
1009
|
-
handleSignalSessionCorruption,
|
|
856
|
+
type: "md", ws, ev, authState: { creds, keys }, signalRepository,
|
|
857
|
+
get user() { return authState.creds.me },
|
|
858
|
+
generateMessageTag, query, waitForMessage, waitForSocketOpen, sendRawMessage, sendNode,
|
|
859
|
+
logout, end, onUnexpectedError, uploadPreKeys, uploadPreKeysToServerIfRequired,
|
|
860
|
+
requestPairingCode, wamBuffer: publicWAMBuffer,
|
|
861
|
+
waitForConnectionUpdate: bindWaitForConnectionUpdate(ev), sendWAMBuffer,
|
|
862
|
+
executeUSyncQuery, onWhatsApp, regeneratePreKeysForRecovery, handleSignalSessionCorruption,
|
|
1010
863
|
listener: (eventName) => {
|
|
1011
|
-
if (typeof ev.listenerCount === "function")
|
|
1012
|
-
|
|
1013
|
-
}
|
|
1014
|
-
if (typeof ev.listeners === "function") {
|
|
1015
|
-
return ev.listeners(eventName)?.length || 0
|
|
1016
|
-
}
|
|
864
|
+
if (typeof ev.listenerCount === "function") return ev.listenerCount(eventName)
|
|
865
|
+
if (typeof ev.listeners === "function") return ev.listeners(eventName)?.length || 0
|
|
1017
866
|
return 0
|
|
1018
|
-
}
|
|
867
|
+
}
|
|
1019
868
|
}
|
|
1020
869
|
}
|
|
1021
|
-
|
|
1022
|
-
* map the websocket error to the right type
|
|
1023
|
-
* so it can be retried by the caller
|
|
1024
|
-
* */
|
|
870
|
+
|
|
1025
871
|
function mapWebSocketError(handler) {
|
|
1026
872
|
return (error) => {
|
|
1027
873
|
handler(new Boom(`WebSocket Error (${error?.message})`, { statusCode: getCodeFromWSError(error), data: error }))
|
|
1028
874
|
}
|
|
1029
|
-
}
|
|
1030
|
-
//# sourceMappingURL=socket.js.map
|
|
875
|
+
}
|