@nexustechpro/baileys 2.0.2 → 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/LICENSE +1 -1
- package/README.md +924 -1299
- package/WAProto/index.js +22 -18
- package/lib/Defaults/baileys-version.json +6 -2
- package/lib/Defaults/index.js +173 -172
- package/lib/Signal/libsignal.js +395 -292
- package/lib/Signal/lid-mapping.js +264 -171
- package/lib/Socket/Client/index.js +2 -2
- package/lib/Socket/Client/types.js +10 -10
- package/lib/Socket/Client/websocket.js +45 -310
- package/lib/Socket/business.js +375 -375
- package/lib/Socket/chats.js +916 -963
- package/lib/Socket/communities.js +430 -430
- package/lib/Socket/groups.js +342 -342
- package/lib/Socket/index.js +21 -22
- package/lib/Socket/messages-recv.js +963 -743
- package/lib/Socket/messages-send.js +273 -321
- package/lib/Socket/mex.js +50 -50
- package/lib/Socket/newsletter.js +148 -148
- package/lib/Socket/nexus-handler.js +296 -247
- package/lib/Socket/registration.js +50 -33
- package/lib/Socket/socket.js +872 -1201
- package/lib/Store/index.js +5 -5
- package/lib/Store/make-cache-manager-store.js +81 -81
- package/lib/Store/make-in-memory-store.js +416 -416
- package/lib/Store/make-ordered-dictionary.js +81 -81
- package/lib/Store/object-repository.js +30 -30
- package/lib/Types/Auth.js +1 -1
- package/lib/Types/Bussines.js +1 -1
- package/lib/Types/Call.js +1 -1
- package/lib/Types/Chat.js +7 -7
- package/lib/Types/Contact.js +1 -1
- package/lib/Types/Events.js +1 -1
- package/lib/Types/GroupMetadata.js +1 -1
- package/lib/Types/Label.js +24 -24
- package/lib/Types/LabelAssociation.js +6 -6
- package/lib/Types/Message.js +10 -10
- package/lib/Types/Newsletter.js +37 -29
- package/lib/Types/Product.js +1 -1
- package/lib/Types/Signal.js +1 -1
- package/lib/Types/Socket.js +2 -2
- package/lib/Types/State.js +55 -12
- package/lib/Types/USync.js +1 -1
- package/lib/Types/index.js +25 -25
- package/lib/Utils/auth-utils.js +264 -256
- package/lib/Utils/baileys-event-stream.js +55 -55
- package/lib/Utils/browser-utils.js +27 -27
- package/lib/Utils/business.js +228 -230
- package/lib/Utils/chat-utils.js +726 -764
- package/lib/Utils/companion-reg-client-utils.js +34 -0
- package/lib/Utils/crypto.js +109 -135
- package/lib/Utils/decode-wa-message.js +342 -314
- package/lib/Utils/event-buffer.js +547 -547
- package/lib/Utils/generics.js +295 -297
- package/lib/Utils/history.js +91 -83
- package/lib/Utils/index.js +25 -20
- package/lib/Utils/key-store.js +17 -0
- package/lib/Utils/link-preview.js +107 -98
- package/lib/Utils/logger.js +2 -2
- package/lib/Utils/lt-hash.js +47 -47
- package/lib/Utils/make-mutex.js +39 -39
- package/lib/Utils/message-retry-manager.js +148 -148
- package/lib/Utils/messages-media.js +579 -535
- package/lib/Utils/messages.js +821 -706
- package/lib/Utils/noise-handler.js +255 -255
- package/lib/Utils/pre-key-manager.js +105 -105
- package/lib/Utils/process-message.js +430 -412
- package/lib/Utils/reporting-utils.js +155 -0
- package/lib/Utils/signal.js +191 -159
- package/lib/Utils/sync-action-utils.js +33 -0
- package/lib/Utils/tc-token-utils.js +162 -0
- package/lib/Utils/use-multi-file-auth-state.js +120 -120
- package/lib/Utils/validate-connection.js +194 -194
- package/lib/WABinary/constants.js +1306 -1300
- package/lib/WABinary/decode.js +237 -237
- package/lib/WABinary/encode.js +232 -232
- package/lib/WABinary/generic-utils.js +252 -211
- package/lib/WABinary/index.js +6 -5
- package/lib/WABinary/jid-utils.js +279 -95
- package/lib/WABinary/types.js +1 -1
- package/lib/WAM/BinaryInfo.js +9 -9
- package/lib/WAM/constants.js +22852 -22852
- package/lib/WAM/encode.js +149 -149
- package/lib/WAM/index.js +3 -3
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +28 -28
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +53 -53
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -26
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -37
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -50
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -28
- package/lib/WAUSync/Protocols/index.js +4 -4
- package/lib/WAUSync/USyncQuery.js +93 -93
- package/lib/WAUSync/USyncUser.js +22 -22
- package/lib/WAUSync/index.js +3 -3
- package/lib/index.js +65 -66
- package/package.json +172 -143
- package/lib/Signal/Group/ciphertext-message.js +0 -12
- package/lib/Signal/Group/group-session-builder.js +0 -30
- package/lib/Signal/Group/group_cipher.js +0 -100
- package/lib/Signal/Group/index.js +0 -12
- package/lib/Signal/Group/keyhelper.js +0 -18
- package/lib/Signal/Group/sender-chain-key.js +0 -26
- package/lib/Signal/Group/sender-key-distribution-message.js +0 -63
- package/lib/Signal/Group/sender-key-message.js +0 -66
- package/lib/Signal/Group/sender-key-name.js +0 -48
- package/lib/Signal/Group/sender-key-record.js +0 -41
- package/lib/Signal/Group/sender-key-state.js +0 -84
- package/lib/Signal/Group/sender-message-key.js +0 -26
package/lib/Socket/socket.js
CHANGED
|
@@ -1,1202 +1,873 @@
|
|
|
1
|
-
import { Boom } from
|
|
2
|
-
import { randomBytes } from
|
|
3
|
-
import { URL } from
|
|
4
|
-
import { promisify } from
|
|
5
|
-
import { proto } from
|
|
6
|
-
import {
|
|
7
|
-
DEF_CALLBACK_PREFIX,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
logger,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
let
|
|
239
|
-
|
|
240
|
-
for (const jid of
|
|
241
|
-
if (isLidUser(jid)) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
logger.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
return
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
)
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
if (
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
if (customPairingCode && customPairingCode?.length !== 8) {
|
|
874
|
-
throw new Error("Custom pairing code must be exactly 8 chars")
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
authState.creds.pairingCode = pairingCode
|
|
878
|
-
authState.creds.me = { id: jidEncode(phoneNumber, "s.whatsapp.net"), name: "~" }
|
|
879
|
-
ev.emit("creds.update", authState.creds)
|
|
880
|
-
|
|
881
|
-
await sendNode({
|
|
882
|
-
tag: "iq",
|
|
883
|
-
attrs: { to: S_WHATSAPP_NET, type: "set", id: generateMessageTag(), xmlns: "md" },
|
|
884
|
-
content: [{
|
|
885
|
-
tag: "link_code_companion_reg",
|
|
886
|
-
attrs: {
|
|
887
|
-
jid: authState.creds.me.id,
|
|
888
|
-
stage: "companion_hello",
|
|
889
|
-
should_show_push_notification: "true"
|
|
890
|
-
},
|
|
891
|
-
content: [
|
|
892
|
-
{ tag: "link_code_pairing_wrapped_companion_ephemeral_pub", attrs: {}, content: await generatePairingKey() },
|
|
893
|
-
{ tag: "companion_server_auth_key_pub", attrs: {}, content: authState.creds.noiseKey.public },
|
|
894
|
-
{ tag: "companion_platform_id", attrs: {}, content: getPlatformId(browser[1]) },
|
|
895
|
-
{ tag: "companion_platform_display", attrs: {}, content: `${browser[1]} (${browser[0]})` },
|
|
896
|
-
{ tag: "link_code_pairing_nonce", attrs: {}, content: "0" }
|
|
897
|
-
]
|
|
898
|
-
}]
|
|
899
|
-
})
|
|
900
|
-
|
|
901
|
-
return authState.creds.pairingCode
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
async function generatePairingKey() {
|
|
905
|
-
const salt = randomBytes(32)
|
|
906
|
-
const randomIv = randomBytes(16)
|
|
907
|
-
const key = await derivePairingCodeKey(authState.creds.pairingCode, salt)
|
|
908
|
-
const ciphered = aesEncryptCTR(authState.creds.pairingEphemeralKeyPair.public, key, randomIv)
|
|
909
|
-
return Buffer.concat([salt, randomIv, ciphered])
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
const sendWAMBuffer = (wamBuffer) => query({
|
|
913
|
-
tag: "iq",
|
|
914
|
-
attrs: { to: S_WHATSAPP_NET, id: generateMessageTag(), xmlns: "w:stats" },
|
|
915
|
-
content: [{ tag: "add", attrs: { t: Math.round(Date.now() / 1000) + "" }, content: wamBuffer }]
|
|
916
|
-
})
|
|
917
|
-
|
|
918
|
-
// ==================== WEBSOCKET EVENT HANDLERS ====================
|
|
919
|
-
ws.on("message", onMessageReceived)
|
|
920
|
-
|
|
921
|
-
ws.on("open", async () => {
|
|
922
|
-
try {
|
|
923
|
-
await validateConnection()
|
|
924
|
-
} catch (err) {
|
|
925
|
-
logger.error({ err }, "Error in validating connection")
|
|
926
|
-
end(err)
|
|
927
|
-
}
|
|
928
|
-
})
|
|
929
|
-
|
|
930
|
-
ws.on("error", (err) => {
|
|
931
|
-
const isNetworkTimeout = err?.code === "ETIMEDOUT" || err?.code === "ECONNREFUSED"
|
|
932
|
-
const isNetworkError = err?.code === "ENOTFOUND" || err?.code === "ECONNRESET" || isNetworkTimeout
|
|
933
|
-
|
|
934
|
-
const errorDetails = {
|
|
935
|
-
message: err?.message || "Unknown error",
|
|
936
|
-
code: err?.code,
|
|
937
|
-
isNetworkError,
|
|
938
|
-
stack: err?.stack
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
if (isNetworkTimeout) {
|
|
942
|
-
logger.warn(errorDetails, "WebSocket connection timeout - network may be unreachable")
|
|
943
|
-
} else if (isNetworkError) {
|
|
944
|
-
logger.warn(errorDetails, "WebSocket network error - will attempt reconnection")
|
|
945
|
-
} else {
|
|
946
|
-
logger.warn(errorDetails, "WebSocket error occurred")
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
// Trigger reconnection on critical errors
|
|
950
|
-
if (isNetworkError && !closed) {
|
|
951
|
-
attemptReconnection("websocket-error").catch(err => {
|
|
952
|
-
logger.error({ err }, "Reconnection attempt failed after WebSocket error")
|
|
953
|
-
})
|
|
954
|
-
}
|
|
955
|
-
})
|
|
956
|
-
|
|
957
|
-
ws.on("close", (code, reason) => {
|
|
958
|
-
const closeReason = reason?.toString() || "Unknown"
|
|
959
|
-
logger.debug({ code, reason: closeReason }, "WebSocket closed")
|
|
960
|
-
|
|
961
|
-
if (!closed) {
|
|
962
|
-
const delayMs = code === 1000 ? 1000 : 2000
|
|
963
|
-
setTimeout(() => {
|
|
964
|
-
attemptReconnection("websocket-close").catch(err => {
|
|
965
|
-
logger.error({ err, code, reason: closeReason }, "Reconnection failed")
|
|
966
|
-
end(new Boom("Connection Terminated", { statusCode: DisconnectReason.connectionClosed }))
|
|
967
|
-
})
|
|
968
|
-
}, delayMs)
|
|
969
|
-
}
|
|
970
|
-
})
|
|
971
|
-
|
|
972
|
-
ws.on("CB:xmlstreamend", () => {
|
|
973
|
-
logger.info("Stream ended by server")
|
|
974
|
-
if (!closed) {
|
|
975
|
-
end(new Boom("Connection Terminated by Server", { statusCode: DisconnectReason.connectionClosed }))
|
|
976
|
-
}
|
|
977
|
-
})
|
|
978
|
-
|
|
979
|
-
// ==================== PAIRING HANDLERS ====================
|
|
980
|
-
ws.on("CB:iq,type:set,pair-device", async (stanza) => {
|
|
981
|
-
await sendNode({
|
|
982
|
-
tag: "iq",
|
|
983
|
-
attrs: { to: S_WHATSAPP_NET, type: "result", id: stanza.attrs.id }
|
|
984
|
-
})
|
|
985
|
-
|
|
986
|
-
const pairDeviceNode = getBinaryNodeChild(stanza, "pair-device")
|
|
987
|
-
const refNodes = getBinaryNodeChildren(pairDeviceNode, "ref")
|
|
988
|
-
const noiseKeyB64 = Buffer.from(creds.noiseKey.public).toString("base64")
|
|
989
|
-
const identityKeyB64 = Buffer.from(creds.signedIdentityKey.public).toString("base64")
|
|
990
|
-
const advB64 = creds.advSecretKey
|
|
991
|
-
|
|
992
|
-
let qrMs = qrTimeout || 60000
|
|
993
|
-
|
|
994
|
-
const genPairQR = () => {
|
|
995
|
-
if (!ws.isOpen) return
|
|
996
|
-
|
|
997
|
-
const refNode = refNodes.shift()
|
|
998
|
-
if (!refNode) {
|
|
999
|
-
end(new Boom("QR refs attempts ended", { statusCode: DisconnectReason.timedOut }))
|
|
1000
|
-
return
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
const ref = refNode.content.toString("utf-8")
|
|
1004
|
-
const qr = [ref, noiseKeyB64, identityKeyB64, advB64].join(",")
|
|
1005
|
-
ev.emit("connection.update", { qr })
|
|
1006
|
-
|
|
1007
|
-
qrTimer = setTimeout(genPairQR, qrMs)
|
|
1008
|
-
qrMs = qrTimeout || 20000
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
genPairQR()
|
|
1012
|
-
})
|
|
1013
|
-
|
|
1014
|
-
ws.on("CB:iq,,pair-success", async (stanza) => {
|
|
1015
|
-
logger.debug("Pair success received")
|
|
1016
|
-
|
|
1017
|
-
try {
|
|
1018
|
-
const { reply, creds: updatedCreds } = configureSuccessfulPairing(stanza, creds)
|
|
1019
|
-
logger.info(
|
|
1020
|
-
{ me: updatedCreds.me, platform: updatedCreds.platform },
|
|
1021
|
-
"Pairing configured successfully"
|
|
1022
|
-
)
|
|
1023
|
-
|
|
1024
|
-
ev.emit("creds.update", updatedCreds)
|
|
1025
|
-
ev.emit("connection.update", { isNewLogin: true, qr: undefined })
|
|
1026
|
-
triggerPreKeyCheck("device-paired", "high")
|
|
1027
|
-
|
|
1028
|
-
await sendNode(reply)
|
|
1029
|
-
} catch (error) {
|
|
1030
|
-
logger.info({ trace: error.stack }, "Error in pairing")
|
|
1031
|
-
end(error)
|
|
1032
|
-
}
|
|
1033
|
-
})
|
|
1034
|
-
|
|
1035
|
-
// ==================== CONNECTION SUCCESS ====================
|
|
1036
|
-
ws.on("CB:success", async (node) => {
|
|
1037
|
-
try {
|
|
1038
|
-
await uploadPreKeysToServerIfRequired()
|
|
1039
|
-
await sendPassiveIq("active")
|
|
1040
|
-
} catch (err) {
|
|
1041
|
-
logger.warn({ err }, "Failed to send initial passive IQ")
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
logger.info("✅ Opened connection to WhatsApp")
|
|
1045
|
-
clearTimeout(qrTimer)
|
|
1046
|
-
|
|
1047
|
-
triggerPreKeyCheck("connection-established", "high")
|
|
1048
|
-
startPreKeyBackgroundMonitor()
|
|
1049
|
-
startSessionCleanup()
|
|
1050
|
-
|
|
1051
|
-
ev.emit("creds.update", { me: { ...authState.creds.me, lid: node.attrs.lid } })
|
|
1052
|
-
ev.emit("connection.update", { connection: "open" })
|
|
1053
|
-
|
|
1054
|
-
startSessionHealthMonitor()
|
|
1055
|
-
reconnectAttempts = 0
|
|
1056
|
-
|
|
1057
|
-
// Handle LID session creation
|
|
1058
|
-
if (node.attrs.lid && authState.creds.me?.id) {
|
|
1059
|
-
const myLID = node.attrs.lid
|
|
1060
|
-
process.nextTick(async () => {
|
|
1061
|
-
try {
|
|
1062
|
-
const myPN = authState.creds.me.id
|
|
1063
|
-
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: myLID, pn: myPN }])
|
|
1064
|
-
|
|
1065
|
-
const { user, device } = jidDecode(myPN)
|
|
1066
|
-
const existingData = await authState.keys.get("device-list", ["_index"])
|
|
1067
|
-
const currentBatch = existingData?.['_index'] || {}
|
|
1068
|
-
currentBatch[user] = [device?.toString() || "0"]
|
|
1069
|
-
|
|
1070
|
-
// Enforce batch size limit
|
|
1071
|
-
const deviceKeys = Object.keys(currentBatch).filter(k => k !== '_index')
|
|
1072
|
-
if (deviceKeys.length > BATCH_SIZE) {
|
|
1073
|
-
deviceKeys.sort()
|
|
1074
|
-
const toRemove = deviceKeys.slice(0, deviceKeys.length - BATCH_SIZE)
|
|
1075
|
-
toRemove.forEach(k => delete currentBatch[k])
|
|
1076
|
-
logger.debug(`Cleaned up ${toRemove.length} old device-list entries (kept ${BATCH_SIZE})`)
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
await authState.keys.set({ "device-list": { "_index": currentBatch } })
|
|
1080
|
-
await signalRepository.migrateSession(myPN, myLID)
|
|
1081
|
-
|
|
1082
|
-
logger.info({ myPN, myLID }, "Own LID session created successfully")
|
|
1083
|
-
} catch (error) {
|
|
1084
|
-
logger.error({ error, lid: myLID }, "Failed to create own LID session")
|
|
1085
|
-
}
|
|
1086
|
-
})
|
|
1087
|
-
}
|
|
1088
|
-
})
|
|
1089
|
-
|
|
1090
|
-
// ==================== ERROR HANDLERS ====================
|
|
1091
|
-
ws.on('CB:stream:error', (node) => {
|
|
1092
|
-
logger.error({ node }, 'Stream errored out')
|
|
1093
|
-
const { reason, statusCode } = getErrorCodeFromStreamError(node)
|
|
1094
|
-
end(new Boom(`Stream Errored (${reason})`, { statusCode, data: node }))
|
|
1095
|
-
|
|
1096
|
-
if (statusCode === 500 || statusCode === 440) {
|
|
1097
|
-
logger.debug("Triggering background pre-key check after stream error")
|
|
1098
|
-
triggerPreKeyCheck("stream-error-recovery", "normal")
|
|
1099
|
-
}
|
|
1100
|
-
})
|
|
1101
|
-
|
|
1102
|
-
ws.on("CB:failure", (node) => {
|
|
1103
|
-
const reason = +(node.attrs.reason || 500)
|
|
1104
|
-
end(new Boom("Connection Failure", { statusCode: reason, data: node.attrs }))
|
|
1105
|
-
})
|
|
1106
|
-
|
|
1107
|
-
ws.on("CB:ib,,downgrade_webclient", () => {
|
|
1108
|
-
end(new Boom("Multi-device beta not joined", { statusCode: DisconnectReason.multideviceMismatch }))
|
|
1109
|
-
})
|
|
1110
|
-
|
|
1111
|
-
ws.on("CB:ib,,offline_preview", (node) => {
|
|
1112
|
-
logger.info("Offline preview received", JSON.stringify(node))
|
|
1113
|
-
sendNode({
|
|
1114
|
-
tag: "ib",
|
|
1115
|
-
attrs: {},
|
|
1116
|
-
content: [{ tag: "offline_batch", attrs: { count: "100" } }]
|
|
1117
|
-
})
|
|
1118
|
-
})
|
|
1119
|
-
|
|
1120
|
-
ws.on("CB:ib,,edge_routing", (node) => {
|
|
1121
|
-
const edgeRoutingNode = getBinaryNodeChild(node, "edge_routing")
|
|
1122
|
-
const routingInfo = getBinaryNodeChild(edgeRoutingNode, "routing_info")
|
|
1123
|
-
|
|
1124
|
-
if (routingInfo?.content) {
|
|
1125
|
-
authState.creds.routingInfo = Buffer.from(routingInfo?.content)
|
|
1126
|
-
ev.emit("creds.update", authState.creds)
|
|
1127
|
-
}
|
|
1128
|
-
})
|
|
1129
|
-
|
|
1130
|
-
// ==================== OFFLINE NOTIFICATIONS ====================
|
|
1131
|
-
let didStartBuffer = false
|
|
1132
|
-
process.nextTick(() => {
|
|
1133
|
-
if (creds.me?.id) {
|
|
1134
|
-
ev.buffer()
|
|
1135
|
-
didStartBuffer = true
|
|
1136
|
-
}
|
|
1137
|
-
ev.emit("connection.update", {
|
|
1138
|
-
connection: "connecting",
|
|
1139
|
-
receivedPendingNotifications: false,
|
|
1140
|
-
qr: undefined
|
|
1141
|
-
})
|
|
1142
|
-
})
|
|
1143
|
-
|
|
1144
|
-
ws.on("CB:ib,,offline", (node) => {
|
|
1145
|
-
const child = getBinaryNodeChild(node, "offline")
|
|
1146
|
-
const offlineNotifs = +(child?.attrs.count || 0)
|
|
1147
|
-
logger.info(`Handled ${offlineNotifs} offline messages/notifications`)
|
|
1148
|
-
|
|
1149
|
-
if (didStartBuffer) {
|
|
1150
|
-
ev.flush()
|
|
1151
|
-
logger.trace("Flushed events for initial buffer")
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
ev.emit("connection.update", { receivedPendingNotifications: true })
|
|
1155
|
-
})
|
|
1156
|
-
|
|
1157
|
-
// ==================== CREDENTIALS UPDATE ====================
|
|
1158
|
-
ev.on("creds.update", (update) => {
|
|
1159
|
-
const name = update.me?.name
|
|
1160
|
-
if (creds.me?.name !== name) {
|
|
1161
|
-
logger.debug({ name }, "Updated pushName")
|
|
1162
|
-
sendNode({ tag: "presence", attrs: { name } }).catch((err) =>
|
|
1163
|
-
logger.warn({ trace: err.stack }, "Error in sending presence update on name change")
|
|
1164
|
-
)
|
|
1165
|
-
}
|
|
1166
|
-
Object.assign(creds, update)
|
|
1167
|
-
})
|
|
1168
|
-
|
|
1169
|
-
// ==================== RETURN SOCKET API ====================
|
|
1170
|
-
return {
|
|
1171
|
-
type: "md",
|
|
1172
|
-
ws,
|
|
1173
|
-
ev,
|
|
1174
|
-
authState: { creds, keys },
|
|
1175
|
-
signalRepository,
|
|
1176
|
-
get user() {
|
|
1177
|
-
return authState.creds.me
|
|
1178
|
-
},
|
|
1179
|
-
generateMessageTag,
|
|
1180
|
-
query,
|
|
1181
|
-
waitForMessage,
|
|
1182
|
-
waitForSocketOpen,
|
|
1183
|
-
sendRawMessage,
|
|
1184
|
-
sendNode,
|
|
1185
|
-
logout,
|
|
1186
|
-
end,
|
|
1187
|
-
onUnexpectedError,
|
|
1188
|
-
uploadPreKeys,
|
|
1189
|
-
uploadPreKeysToServerIfRequired,
|
|
1190
|
-
requestPairingCode,
|
|
1191
|
-
wamBuffer: publicWAMBuffer,
|
|
1192
|
-
waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),
|
|
1193
|
-
sendWAMBuffer,
|
|
1194
|
-
executeUSyncQuery,
|
|
1195
|
-
onWhatsApp,
|
|
1196
|
-
listener: (eventName) => {
|
|
1197
|
-
if (typeof ev.listenerCount === "function") return ev.listenerCount(eventName)
|
|
1198
|
-
if (typeof ev.listener === "function") return ev.listener(eventName)?.length || 0
|
|
1199
|
-
return 0
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
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'
|
|
58
|
+
|
|
59
|
+
// ─── Module-scope helpers ──────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Map a raw WebSocket error into a Boom so callers
|
|
63
|
+
* can inspect the statusCode / DisconnectReason.
|
|
64
|
+
*/
|
|
65
|
+
const mapWebSocketError = (handler) => (error) =>
|
|
66
|
+
handler(
|
|
67
|
+
new Boom(`WebSocket Error (${error?.message})`, {
|
|
68
|
+
statusCode: getCodeFromWSError(error),
|
|
69
|
+
data: error
|
|
70
|
+
})
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
// ─── Factory ──────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
export const makeSocket = (config) => {
|
|
76
|
+
const {
|
|
77
|
+
waWebSocketUrl,
|
|
78
|
+
connectTimeoutMs,
|
|
79
|
+
logger,
|
|
80
|
+
keepAliveIntervalMs,
|
|
81
|
+
browser,
|
|
82
|
+
auth: authState,
|
|
83
|
+
printQRInTerminal,
|
|
84
|
+
defaultQueryTimeoutMs,
|
|
85
|
+
transactionOpts,
|
|
86
|
+
qrTimeout,
|
|
87
|
+
makeSignalRepository
|
|
88
|
+
} = config
|
|
89
|
+
|
|
90
|
+
if (printQRInTerminal) {
|
|
91
|
+
logger?.warn(
|
|
92
|
+
{},
|
|
93
|
+
'⚠️ printQRInTerminal is deprecated. Listen to connection.update and handle QR yourself.'
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const url = typeof waWebSocketUrl === 'string' ? new URL(waWebSocketUrl) : waWebSocketUrl
|
|
98
|
+
|
|
99
|
+
if (config.mobile || url.protocol === 'tcp:')
|
|
100
|
+
throw new Boom('Mobile API not supported', { statusCode: DisconnectReason.loggedOut })
|
|
101
|
+
|
|
102
|
+
if (url.protocol === 'wss:' && authState?.creds?.routingInfo)
|
|
103
|
+
url.searchParams.append('ED', authState.creds.routingInfo.toString('base64url'))
|
|
104
|
+
|
|
105
|
+
const ephemeralKeyPair = Curve.generateKeyPair()
|
|
106
|
+
const noise = makeNoiseHandler({
|
|
107
|
+
keyPair: ephemeralKeyPair,
|
|
108
|
+
NOISE_HEADER: NOISE_WA_HEADER,
|
|
109
|
+
logger,
|
|
110
|
+
routingInfo: authState?.creds?.routingInfo
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const ws = new WebSocketClient(url, config)
|
|
114
|
+
logger.info({ url: url.toString() }, 'Initiating WebSocket connection')
|
|
115
|
+
ws.connect()
|
|
116
|
+
|
|
117
|
+
const ev = makeEventBuffer(logger)
|
|
118
|
+
const { creds } = authState
|
|
119
|
+
const keys = addTransactionCapability(authState.keys, logger, transactionOpts)
|
|
120
|
+
const signalRepository = makeSignalRepository({ creds, keys }, logger, pnFromLIDUSync)
|
|
121
|
+
const publicWAMBuffer = new BinaryInfo()
|
|
122
|
+
const uqTagId = generateMdTagPrefix()
|
|
123
|
+
const sendPromise = promisify(ws.send)
|
|
124
|
+
|
|
125
|
+
// State
|
|
126
|
+
let epoch = 1
|
|
127
|
+
let lastDateRecv
|
|
128
|
+
let lastUploadTime = 0
|
|
129
|
+
let uploadPreKeysPromise = null
|
|
130
|
+
let closed = false
|
|
131
|
+
let keepAliveReq
|
|
132
|
+
let qrTimer
|
|
133
|
+
let serverTimeOffsetMs = 0
|
|
134
|
+
let didStartBuffer = false
|
|
135
|
+
|
|
136
|
+
/** Socket end handlers — registered via registerSocketEndHandler() */
|
|
137
|
+
const socketEndHandlers = []
|
|
138
|
+
|
|
139
|
+
const generateMessageTag = () => `${uqTagId}${epoch++}`
|
|
140
|
+
|
|
141
|
+
// ─── Transport ──────────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
const sendRawMessage = async (data) => {
|
|
144
|
+
if (!ws.isOpen)
|
|
145
|
+
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
|
146
|
+
const bytes = noise.encodeFrame(data)
|
|
147
|
+
await promiseTimeout(connectTimeoutMs, async (resolve, reject) => {
|
|
148
|
+
try { await sendPromise.call(ws, bytes); resolve() }
|
|
149
|
+
catch (error) { reject(error) }
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const sendNode = (frame) => {
|
|
154
|
+
if (logger.level === 'trace') logger.trace({ xml: binaryNodeToString(frame), msg: 'xml send' })
|
|
155
|
+
return sendRawMessage(encodeBinaryNode(frame))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ─── Query / messaging ──────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
const waitForMessage = async (msgId, timeoutMs = defaultQueryTimeoutMs) => {
|
|
161
|
+
let onRecv, onErr
|
|
162
|
+
try {
|
|
163
|
+
return await promiseTimeout(timeoutMs, (resolve, reject) => {
|
|
164
|
+
onRecv = (data) => resolve(data)
|
|
165
|
+
onErr = (err) =>
|
|
166
|
+
reject(err || new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed }))
|
|
167
|
+
ws.on(`TAG:${msgId}`, onRecv)
|
|
168
|
+
ws.on('close', onErr)
|
|
169
|
+
ws.on('error', onErr)
|
|
170
|
+
return () => reject(new Boom('Query Cancelled'))
|
|
171
|
+
})
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error instanceof Boom && error.output?.statusCode === DisconnectReason.timedOut) {
|
|
174
|
+
logger?.warn?.({ msgId }, 'timed out waiting for message')
|
|
175
|
+
return undefined
|
|
176
|
+
}
|
|
177
|
+
throw error
|
|
178
|
+
} finally {
|
|
179
|
+
if (onRecv) ws.off(`TAG:${msgId}`, onRecv)
|
|
180
|
+
if (onErr) { ws.off('close', onErr); ws.off('error', onErr) }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const query = async (node, timeoutMs) => {
|
|
185
|
+
if (!node.attrs.id) node.attrs.id = generateMessageTag()
|
|
186
|
+
const msgId = node.attrs.id
|
|
187
|
+
const result = await promiseTimeout(timeoutMs, async (resolve, reject) => {
|
|
188
|
+
const result = waitForMessage(msgId, timeoutMs).catch(reject)
|
|
189
|
+
sendNode(node).then(async () => resolve(await result)).catch(reject)
|
|
190
|
+
})
|
|
191
|
+
if (result && 'tag' in result) assertNodeErrorFree(result)
|
|
192
|
+
return result
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ─── USync ──────────────────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
const executeUSyncQuery = async (usyncQuery) => {
|
|
198
|
+
if (usyncQuery.protocols.length === 0)
|
|
199
|
+
throw new Boom('USyncQuery must have at least one protocol')
|
|
200
|
+
const userNodes = usyncQuery.users.map((user) => ({
|
|
201
|
+
tag: 'user',
|
|
202
|
+
attrs: { jid: !user.phone ? user.id : undefined },
|
|
203
|
+
content: usyncQuery.protocols.map((a) => a.getUserElement(user)).filter((a) => a !== null)
|
|
204
|
+
}))
|
|
205
|
+
const iq = {
|
|
206
|
+
tag: 'iq',
|
|
207
|
+
attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'usync' },
|
|
208
|
+
content: [{
|
|
209
|
+
tag: 'usync',
|
|
210
|
+
attrs: {
|
|
211
|
+
context: usyncQuery.context,
|
|
212
|
+
mode: usyncQuery.mode,
|
|
213
|
+
sid: generateMessageTag(),
|
|
214
|
+
last: 'true',
|
|
215
|
+
index: '0'
|
|
216
|
+
},
|
|
217
|
+
content: [
|
|
218
|
+
{ tag: 'query', attrs: {}, content: usyncQuery.protocols.map((a) => a.getQueryElement()) },
|
|
219
|
+
{ tag: 'list', attrs: {}, content: userNodes }
|
|
220
|
+
]
|
|
221
|
+
}]
|
|
222
|
+
}
|
|
223
|
+
return usyncQuery.parseUSyncQueryResult(await query(iq))
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function pnFromLIDUSync(jids) {
|
|
227
|
+
const usyncQuery = new USyncQuery().withLIDProtocol().withContext('background')
|
|
228
|
+
for (const jid of jids) {
|
|
229
|
+
if (isLidUser(jid)) { logger?.warn('LID user found in LID fetch call'); continue }
|
|
230
|
+
usyncQuery.withUser(new USyncUser().withId(jid))
|
|
231
|
+
}
|
|
232
|
+
if (usyncQuery.users.length === 0) return []
|
|
233
|
+
const results = await executeUSyncQuery(usyncQuery)
|
|
234
|
+
return results ? results.list.filter((a) => !!a.lid).map(({ lid, id }) => ({ pn: id, lid })) : []
|
|
235
|
+
}
|
|
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
|
+
|
|
253
|
+
// ─── Pre-keys ───────────────────────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
const getAvailablePreKeysOnServer = async () => {
|
|
256
|
+
const result = await query({
|
|
257
|
+
tag: 'iq',
|
|
258
|
+
attrs: { id: generateMessageTag(), xmlns: 'encrypt', type: 'get', to: S_WHATSAPP_NET },
|
|
259
|
+
content: [{ tag: 'count', attrs: {} }]
|
|
260
|
+
})
|
|
261
|
+
return +getBinaryNodeChild(result, 'count').attrs.value
|
|
262
|
+
}
|
|
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
|
+
|
|
275
|
+
const uploadPreKeys = async (count = MIN_PREKEY_COUNT, retryCount = 0) => {
|
|
276
|
+
// Rate-limit guard — only on the first attempt, not retries
|
|
277
|
+
if (retryCount === 0 && Date.now() - lastUploadTime < MIN_UPLOAD_INTERVAL) {
|
|
278
|
+
logger.debug(`Skipping upload — only ${Date.now() - lastUploadTime}ms since last upload`)
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
// Dedup: if an upload is already in-flight, wait for it
|
|
282
|
+
if (uploadPreKeysPromise) {
|
|
283
|
+
logger.debug('Pre-key upload in progress, waiting...')
|
|
284
|
+
await uploadPreKeysPromise
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
const uploadLogic = async () => {
|
|
288
|
+
logger.info({ count, retryCount }, 'Uploading pre-keys')
|
|
289
|
+
// Generate keys inside a transaction to prevent ID collisions on retry
|
|
290
|
+
const node = await keys.transaction(async () => {
|
|
291
|
+
const { update, node } = await getNextPreKeysNode({ creds, keys }, count)
|
|
292
|
+
ev.emit('creds.update', update)
|
|
293
|
+
return node
|
|
294
|
+
}, creds?.me?.id || 'upload-pre-keys')
|
|
295
|
+
try {
|
|
296
|
+
await query(node)
|
|
297
|
+
logger.info({ count }, '✅ Pre-keys uploaded successfully')
|
|
298
|
+
lastUploadTime = Date.now()
|
|
299
|
+
} catch (uploadError) {
|
|
300
|
+
logger.error({ uploadError: uploadError.toString(), count }, 'Failed to upload pre-keys')
|
|
301
|
+
if (retryCount < 3) {
|
|
302
|
+
const backoffDelay = Math.min(1000 * Math.pow(2, retryCount), 10000)
|
|
303
|
+
logger.info(`Retrying pre-key upload in ${backoffDelay}ms`)
|
|
304
|
+
await new Promise((resolve) => setTimeout(resolve, backoffDelay))
|
|
305
|
+
return uploadPreKeys(count, retryCount + 1)
|
|
306
|
+
}
|
|
307
|
+
throw uploadError
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
uploadPreKeysPromise = Promise.race([
|
|
311
|
+
uploadLogic(),
|
|
312
|
+
new Promise((_, reject) =>
|
|
313
|
+
setTimeout(() => reject(new Boom('Pre-key upload timeout', { statusCode: 408 })), UPLOAD_TIMEOUT)
|
|
314
|
+
)
|
|
315
|
+
])
|
|
316
|
+
try { await uploadPreKeysPromise } finally { uploadPreKeysPromise = null }
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const uploadPreKeysToServerIfRequired = async () => {
|
|
320
|
+
try {
|
|
321
|
+
const preKeyCount = await getAvailablePreKeysOnServer()
|
|
322
|
+
logger.info(`${preKeyCount} pre-keys found on server`)
|
|
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
|
|
333
|
+
await uploadPreKeys(uploadCount)
|
|
334
|
+
} else {
|
|
335
|
+
logger.info(`✅ PreKey validation passed — Server: ${preKeyCount}, prekey ${currentPreKeyId} exists`)
|
|
336
|
+
}
|
|
337
|
+
} catch (error) {
|
|
338
|
+
logger.error({ error }, 'Failed to check/upload pre-keys during init')
|
|
339
|
+
// Non-fatal — allow connection to continue
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ─── Key-bundle digest & signed pre-key rotation ────────────────────────────
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Validate our current key-bundle against the server.
|
|
347
|
+
* If the server returns no digest node our keys are out of sync —
|
|
348
|
+
* force a pre-key upload and surface the error so the caller can decide.
|
|
349
|
+
*/
|
|
350
|
+
const digestKeyBundle = async () => {
|
|
351
|
+
const res = await query({
|
|
352
|
+
tag: 'iq',
|
|
353
|
+
attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'encrypt' },
|
|
354
|
+
content: [{ tag: 'digest', attrs: {} }]
|
|
355
|
+
})
|
|
356
|
+
const digestNode = getBinaryNodeChild(res, 'digest')
|
|
357
|
+
if (!digestNode) {
|
|
358
|
+
await uploadPreKeys()
|
|
359
|
+
throw new Error('encrypt/get digest returned no digest node')
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Rotate our signed pre-key on the server.
|
|
365
|
+
* Should be called periodically (e.g. every 7 days) to keep sessions healthy.
|
|
366
|
+
*/
|
|
367
|
+
const rotateSignedPreKey = async () => {
|
|
368
|
+
const newId = (creds.signedPreKey.keyId || 0) + 1
|
|
369
|
+
const skey = await signedKeyPair(creds.signedIdentityKey, newId)
|
|
370
|
+
await query({
|
|
371
|
+
tag: 'iq',
|
|
372
|
+
attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'encrypt' },
|
|
373
|
+
content: [{ tag: 'rotate', attrs: {}, content: [xmppSignedPreKey(skey)] }]
|
|
374
|
+
})
|
|
375
|
+
ev.emit('creds.update', { signedPreKey: skey })
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ─── Server time offset ─────────────────────────────────────────────────────
|
|
379
|
+
|
|
380
|
+
const updateServerTimeOffset = ({ attrs }) => {
|
|
381
|
+
const tValue = attrs?.t
|
|
382
|
+
if (!tValue) return
|
|
383
|
+
const parsed = Number(tValue)
|
|
384
|
+
if (Number.isNaN(parsed) || parsed <= 0) return
|
|
385
|
+
serverTimeOffsetMs = parsed * 1000 - Date.now()
|
|
386
|
+
logger.debug({ offset: serverTimeOffsetMs }, 'Calculated server time offset')
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ─── Unified session telemetry ───────────────────────────────────────────────
|
|
390
|
+
|
|
391
|
+
const getUnifiedSessionId = () => {
|
|
392
|
+
const offsetMs = 3 * TimeMs.Day
|
|
393
|
+
const now = Date.now() + serverTimeOffsetMs
|
|
394
|
+
return ((now + offsetMs) % TimeMs.Week).toString()
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const sendUnifiedSession = async () => {
|
|
398
|
+
if (!ws.isOpen) return
|
|
399
|
+
try {
|
|
400
|
+
await sendNode({
|
|
401
|
+
tag: 'ib',
|
|
402
|
+
attrs: {},
|
|
403
|
+
content: [{ tag: 'unified_session', attrs: { id: getUnifiedSessionId() } }]
|
|
404
|
+
})
|
|
405
|
+
} catch (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
|
|
439
|
+
}
|
|
440
|
+
ev.emit('connection.update', { reachoutTimeLock: result })
|
|
441
|
+
return result
|
|
442
|
+
}
|
|
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
|
+
|
|
456
|
+
// ─── Connection lifecycle ────────────────────────────────────────────────────
|
|
457
|
+
|
|
458
|
+
const onUnexpectedError = (err, msg) => {
|
|
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
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const awaitNextMessage = async (sendMsg) => {
|
|
468
|
+
if (!ws.isOpen)
|
|
469
|
+
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
|
470
|
+
let onOpen, onClose
|
|
471
|
+
const result = promiseTimeout(connectTimeoutMs, (resolve, reject) => {
|
|
472
|
+
onOpen = resolve
|
|
473
|
+
onClose = mapWebSocketError(reject)
|
|
474
|
+
ws.on('frame', onOpen)
|
|
475
|
+
ws.on('close', onClose)
|
|
476
|
+
ws.on('error', onClose)
|
|
477
|
+
}).finally(() => {
|
|
478
|
+
ws.off('frame', onOpen)
|
|
479
|
+
ws.off('close', onClose)
|
|
480
|
+
ws.off('error', onClose)
|
|
481
|
+
})
|
|
482
|
+
if (sendMsg) sendRawMessage(sendMsg).catch(onClose)
|
|
483
|
+
return result
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const validateConnection = async () => {
|
|
487
|
+
const helloMsg = proto.HandshakeMessage.fromObject({ clientHello: { ephemeral: ephemeralKeyPair.public } })
|
|
488
|
+
logger.info({ browser, helloMsg }, 'Connected to WhatsApp')
|
|
489
|
+
const init = proto.HandshakeMessage.encode(helloMsg).finish()
|
|
490
|
+
const result = await awaitNextMessage(init)
|
|
491
|
+
const handshake = proto.HandshakeMessage.decode(result)
|
|
492
|
+
logger.trace({ handshake }, 'Handshake received from WhatsApp')
|
|
493
|
+
const keyEnc = await noise.processHandshake(handshake, creds.noiseKey)
|
|
494
|
+
const node = !creds.me ? generateRegistrationNode(creds, config) : generateLoginNode(creds.me.id, config)
|
|
495
|
+
logger.info({ node }, !creds.me ? 'Attempting registration...' : 'Logging in...')
|
|
496
|
+
const payloadEnc = noise.encrypt(proto.ClientPayload.encode(node).finish())
|
|
497
|
+
await sendRawMessage(
|
|
498
|
+
proto.HandshakeMessage.encode({ clientFinish: { static: keyEnc, payload: payloadEnc } }).finish()
|
|
499
|
+
)
|
|
500
|
+
await noise.finishInit()
|
|
501
|
+
startKeepAliveRequest()
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const waitForSocketOpen = async () => {
|
|
505
|
+
if (ws.isOpen) return
|
|
506
|
+
if (ws.isClosed || ws.isClosing)
|
|
507
|
+
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
|
508
|
+
let onOpen, onClose
|
|
509
|
+
await new Promise((resolve, reject) => {
|
|
510
|
+
onOpen = () => resolve(undefined)
|
|
511
|
+
onClose = mapWebSocketError(reject)
|
|
512
|
+
ws.on('open', onOpen)
|
|
513
|
+
ws.on('close', onClose)
|
|
514
|
+
ws.on('error', onClose)
|
|
515
|
+
}).finally(() => {
|
|
516
|
+
ws.off('open', onOpen)
|
|
517
|
+
ws.off('close', onClose)
|
|
518
|
+
ws.off('error', onClose)
|
|
519
|
+
})
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Keep-alive: ping WA every keepAliveIntervalMs.
|
|
524
|
+
* If the server stops responding (diff > interval + 5s) the connection
|
|
525
|
+
* is considered lost and we call end() — the consumer handles reconnection.
|
|
526
|
+
*/
|
|
527
|
+
const startKeepAliveRequest = () => {
|
|
528
|
+
keepAliveReq = setInterval(() => {
|
|
529
|
+
if (!lastDateRecv) lastDateRecv = new Date()
|
|
530
|
+
const diff = Date.now() - lastDateRecv.getTime()
|
|
531
|
+
if (diff > keepAliveIntervalMs + 5000) {
|
|
532
|
+
void end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
|
|
533
|
+
} else if (ws.isOpen) {
|
|
534
|
+
query({
|
|
535
|
+
tag: 'iq',
|
|
536
|
+
attrs: { id: generateMessageTag(), to: S_WHATSAPP_NET, type: 'get', xmlns: 'w:p' },
|
|
537
|
+
content: [{ tag: 'ping', attrs: {} }]
|
|
538
|
+
}).catch((err) => logger.error({ trace: err.stack }, 'Error in sending keep alive'))
|
|
539
|
+
} else {
|
|
540
|
+
logger.warn('Keep alive called when WS not open')
|
|
541
|
+
}
|
|
542
|
+
}, keepAliveIntervalMs)
|
|
543
|
+
}
|
|
544
|
+
|
|
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) => {
|
|
552
|
+
if (closed) { logger.trace({ trace: error?.stack }, 'Connection already closed'); return }
|
|
553
|
+
closed = true
|
|
554
|
+
logger.info({ trace: error?.stack }, error ? 'connection errored' : 'connection closed')
|
|
555
|
+
clearInterval(keepAliveReq)
|
|
556
|
+
clearTimeout(qrTimer)
|
|
557
|
+
ws.removeAllListeners('close')
|
|
558
|
+
ws.removeAllListeners('open')
|
|
559
|
+
ws.removeAllListeners('message')
|
|
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
|
+
}
|
|
568
|
+
ev.emit('connection.update', { connection: 'close', lastDisconnect: { error, date: new Date() } })
|
|
569
|
+
ev.removeAllListeners('connection.update')
|
|
570
|
+
ev.destroy()
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const sendPassiveIq = (tag) =>
|
|
574
|
+
query({
|
|
575
|
+
tag: 'iq',
|
|
576
|
+
attrs: { to: S_WHATSAPP_NET, xmlns: 'passive', type: 'set' },
|
|
577
|
+
content: [{ tag, attrs: {} }]
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
const logout = async (msg) => {
|
|
581
|
+
const jid = authState.creds.me?.id
|
|
582
|
+
if (jid) {
|
|
583
|
+
await sendNode({
|
|
584
|
+
tag: 'iq',
|
|
585
|
+
attrs: { to: S_WHATSAPP_NET, type: 'set', id: generateMessageTag(), xmlns: 'md' },
|
|
586
|
+
content: [{ tag: 'remove-companion-device', attrs: { jid, reason: 'user_initiated' } }]
|
|
587
|
+
})
|
|
588
|
+
}
|
|
589
|
+
void end(new Boom(msg || 'Intentional Logout', { statusCode: DisconnectReason.loggedOut }))
|
|
590
|
+
}
|
|
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
|
+
|
|
598
|
+
// ─── Pairing ─────────────────────────────────────────────────────────────────
|
|
599
|
+
|
|
600
|
+
const requestPairingCode = async (phoneNumber, customPairingCode) => {
|
|
601
|
+
await waitForSocketOpen()
|
|
602
|
+
// Brief stabilisation delay — ensures the WS open event has fully propagated
|
|
603
|
+
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
604
|
+
const pairingCode = customPairingCode ?? bytesToCrockford(randomBytes(5))
|
|
605
|
+
if (customPairingCode && customPairingCode?.length !== 8)
|
|
606
|
+
throw new Error('Custom pairing code must be exactly 8 chars')
|
|
607
|
+
authState.creds.pairingCode = pairingCode
|
|
608
|
+
authState.creds.me = { id: jidEncode(phoneNumber, 's.whatsapp.net'), name: '~' }
|
|
609
|
+
ev.emit('creds.update', authState.creds)
|
|
610
|
+
await sendNode({
|
|
611
|
+
tag: 'iq',
|
|
612
|
+
attrs: { to: S_WHATSAPP_NET, type: 'set', id: generateMessageTag(), xmlns: 'md' },
|
|
613
|
+
content: [{
|
|
614
|
+
tag: 'link_code_companion_reg',
|
|
615
|
+
attrs: { jid: authState.creds.me.id, stage: 'companion_hello', should_show_push_notification: 'true' },
|
|
616
|
+
content: [
|
|
617
|
+
{ tag: 'link_code_pairing_wrapped_companion_ephemeral_pub', attrs: {}, content: await generatePairingKey() },
|
|
618
|
+
{ tag: 'companion_server_auth_key_pub', attrs: {}, content: authState.creds.noiseKey.public },
|
|
619
|
+
{ tag: 'companion_platform_id', attrs: {}, content: getCompanionPlatformId(browser) },
|
|
620
|
+
{ tag: 'companion_platform_display', attrs: {}, content: `${browser[1]} (${browser[0]})` },
|
|
621
|
+
{ tag: 'link_code_pairing_nonce', attrs: {}, content: '0' }
|
|
622
|
+
]
|
|
623
|
+
}]
|
|
624
|
+
})
|
|
625
|
+
return authState.creds.pairingCode
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async function generatePairingKey() {
|
|
629
|
+
const salt = randomBytes(32)
|
|
630
|
+
const randomIv = randomBytes(16)
|
|
631
|
+
const key = await derivePairingCodeKey(authState.creds.pairingCode, salt)
|
|
632
|
+
const ciphered = aesEncryptCTR(authState.creds.pairingEphemeralKeyPair.public, key, randomIv)
|
|
633
|
+
return Buffer.concat([salt, randomIv, ciphered])
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// ─── Incoming message processing ────────────────────────────────────────────
|
|
637
|
+
|
|
638
|
+
const onMessageReceived = (data) => {
|
|
639
|
+
noise.decodeFrame(data, (frame) => {
|
|
640
|
+
lastDateRecv = new Date()
|
|
641
|
+
let anyTriggered = ws.emit('frame', frame)
|
|
642
|
+
if (!(frame instanceof Uint8Array)) {
|
|
643
|
+
const msgId = frame.attrs.id
|
|
644
|
+
if (logger.level === 'trace') logger.trace({ xml: binaryNodeToString(frame), msg: 'recv xml' })
|
|
645
|
+
anyTriggered = ws.emit(`${DEF_TAG_PREFIX}${msgId}`, frame) || anyTriggered
|
|
646
|
+
const l0 = frame.tag
|
|
647
|
+
const l1 = frame.attrs || {}
|
|
648
|
+
const l2 = Array.isArray(frame.content) ? frame.content[0]?.tag : ''
|
|
649
|
+
for (const key of Object.keys(l1)) {
|
|
650
|
+
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]},${l2}`, frame) || anyTriggered
|
|
651
|
+
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]}`, frame) || anyTriggered
|
|
652
|
+
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}`, frame) || anyTriggered
|
|
653
|
+
}
|
|
654
|
+
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},,${l2}`, frame) || anyTriggered
|
|
655
|
+
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0}`, frame) || anyTriggered
|
|
656
|
+
if (!anyTriggered && logger.level === 'debug')
|
|
657
|
+
logger.debug({ unhandled: true, msgId, fromMe: false, frame }, 'Unhandled communication received')
|
|
658
|
+
}
|
|
659
|
+
})
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// ─── WebSocket event bindings ────────────────────────────────────────────────
|
|
663
|
+
|
|
664
|
+
ws.on('message', onMessageReceived)
|
|
665
|
+
|
|
666
|
+
ws.on('open', async () => {
|
|
667
|
+
try { await validateConnection() }
|
|
668
|
+
catch (err) { logger.error({ err }, 'error in validating connection'); void end(err) }
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
ws.on('error', mapWebSocketError(end))
|
|
672
|
+
|
|
673
|
+
ws.on('close', () => void end(new Boom('Connection Terminated', { statusCode: DisconnectReason.connectionClosed })))
|
|
674
|
+
|
|
675
|
+
ws.on('CB:xmlstreamend', () => {
|
|
676
|
+
logger.info('Stream ended by server')
|
|
677
|
+
if (!closed) void end(new Boom('Connection Terminated by Server', { statusCode: DisconnectReason.connectionClosed }))
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
// ─── QR pairing ─────────────────────────────────────────────────────────────
|
|
681
|
+
|
|
682
|
+
ws.on('CB:iq,type:set,pair-device', async (stanza) => {
|
|
683
|
+
await sendNode({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'result', id: stanza.attrs.id } })
|
|
684
|
+
const pairDeviceNode = getBinaryNodeChild(stanza, 'pair-device')
|
|
685
|
+
const refNodes = getBinaryNodeChildren(pairDeviceNode, 'ref')
|
|
686
|
+
const noiseKeyB64 = Buffer.from(creds.noiseKey.public).toString('base64')
|
|
687
|
+
const identityKeyB64 = Buffer.from(creds.signedIdentityKey.public).toString('base64')
|
|
688
|
+
const advB64 = creds.advSecretKey
|
|
689
|
+
let qrMs = qrTimeout || 60000
|
|
690
|
+
const genPairQR = () => {
|
|
691
|
+
if (!ws.isOpen) return
|
|
692
|
+
const refNode = refNodes.shift()
|
|
693
|
+
if (!refNode) { void end(new Boom('QR refs attempts ended', { statusCode: DisconnectReason.timedOut })); return }
|
|
694
|
+
const ref = refNode.content.toString('utf-8')
|
|
695
|
+
// Use buildPairingQRData so the browser tuple is included in the QR payload
|
|
696
|
+
const qr = buildPairingQRData(ref, noiseKeyB64, identityKeyB64, advB64, browser)
|
|
697
|
+
ev.emit('connection.update', { qr })
|
|
698
|
+
qrTimer = setTimeout(genPairQR, qrMs)
|
|
699
|
+
qrMs = qrTimeout || 20000
|
|
700
|
+
}
|
|
701
|
+
genPairQR()
|
|
702
|
+
})
|
|
703
|
+
|
|
704
|
+
ws.on('CB:iq,,pair-success', async (stanza) => {
|
|
705
|
+
logger.debug('Pair success received')
|
|
706
|
+
try {
|
|
707
|
+
updateServerTimeOffset(stanza)
|
|
708
|
+
const { reply, creds: updatedCreds } = configureSuccessfulPairing(stanza, creds)
|
|
709
|
+
logger.info({ me: updatedCreds.me, platform: updatedCreds.platform }, 'Pairing configured successfully')
|
|
710
|
+
ev.emit('creds.update', updatedCreds)
|
|
711
|
+
ev.emit('connection.update', { isNewLogin: true, qr: undefined })
|
|
712
|
+
await sendNode(reply)
|
|
713
|
+
void sendUnifiedSession()
|
|
714
|
+
} catch (error) {
|
|
715
|
+
logger.info({ trace: error.stack }, 'Error in pairing')
|
|
716
|
+
void end(error)
|
|
717
|
+
}
|
|
718
|
+
})
|
|
719
|
+
|
|
720
|
+
// ─── Login complete ──────────────────────────────────────────────────────────
|
|
721
|
+
|
|
722
|
+
ws.on('CB:success', async (node) => {
|
|
723
|
+
try {
|
|
724
|
+
updateServerTimeOffset(node)
|
|
725
|
+
await uploadPreKeysToServerIfRequired()
|
|
726
|
+
await sendPassiveIq('active')
|
|
727
|
+
try { await digestKeyBundle() }
|
|
728
|
+
catch (e) { logger.warn({ e }, 'failed to run digest after login') }
|
|
729
|
+
} catch (err) {
|
|
730
|
+
logger.warn({ err }, 'Failed to send initial passive IQ')
|
|
731
|
+
}
|
|
732
|
+
logger.info('✅ Opened connection to WhatsApp')
|
|
733
|
+
clearTimeout(qrTimer)
|
|
734
|
+
ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } })
|
|
735
|
+
ev.emit('connection.update', { connection: 'open' })
|
|
736
|
+
void sendUnifiedSession()
|
|
737
|
+
|
|
738
|
+
if (node.attrs.lid && authState.creds.me?.id) {
|
|
739
|
+
const myLID = node.attrs.lid
|
|
740
|
+
process.nextTick(async () => {
|
|
741
|
+
try {
|
|
742
|
+
const myPN = authState.creds.me.id
|
|
743
|
+
// Store own LID-PN mapping
|
|
744
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: myLID, pn: myPN }])
|
|
745
|
+
// Build device-list using index-based batching to avoid unbounded key growth
|
|
746
|
+
const { user, device } = jidDecode(myPN)
|
|
747
|
+
const currentBatch = await migrateIndexKey(authState.keys, 'device-list')
|
|
748
|
+
currentBatch[user] = [device?.toString() || '0']
|
|
749
|
+
const deviceKeys = Object.keys(currentBatch)
|
|
750
|
+
if (deviceKeys.length > BATCH_SIZE) {
|
|
751
|
+
deviceKeys.sort()
|
|
752
|
+
deviceKeys.slice(0, deviceKeys.length - BATCH_SIZE).forEach((k) => delete currentBatch[k])
|
|
753
|
+
}
|
|
754
|
+
await authState.keys.set({ 'device-list': { index: currentBatch } })
|
|
755
|
+
// Migrate own session from PN → LID
|
|
756
|
+
await signalRepository.migrateSession(myPN, myLID)
|
|
757
|
+
logger.info({ myPN, myLID }, 'Own LID session created successfully')
|
|
758
|
+
// Batch-migrate any remaining PN sessions to LID
|
|
759
|
+
if (signalRepository.migrateAllPNSessionsToLID) {
|
|
760
|
+
try {
|
|
761
|
+
const migrated = await signalRepository.migrateAllPNSessionsToLID()
|
|
762
|
+
if (migrated > 0) logger.info({ migrated }, 'Batch-migrated PN sessions to LID on connect')
|
|
763
|
+
} catch (migErr) {
|
|
764
|
+
logger.warn({ error: migErr }, 'Failed to batch-migrate PN sessions to LID')
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
} catch (error) {
|
|
768
|
+
logger.error({ error, lid: myLID }, 'Failed to create own LID session')
|
|
769
|
+
}
|
|
770
|
+
})
|
|
771
|
+
}
|
|
772
|
+
})
|
|
773
|
+
|
|
774
|
+
// ─── Stream / connection error handlers ─────────────────────────────────────
|
|
775
|
+
|
|
776
|
+
ws.on('CB:stream:error', (node) => {
|
|
777
|
+
const [reasonNode] = getAllBinaryNodeChildren(node)
|
|
778
|
+
logger.error({ reasonNode, fullErrorNode: node }, 'Stream errored out')
|
|
779
|
+
const { reason, statusCode } = getErrorCodeFromStreamError(node)
|
|
780
|
+
void end(new Boom(`Stream Errored (${reason})`, { statusCode, data: reasonNode || node }))
|
|
781
|
+
})
|
|
782
|
+
|
|
783
|
+
ws.on('CB:failure', (node) => {
|
|
784
|
+
const reason = +(node.attrs.reason || 500)
|
|
785
|
+
void end(new Boom('Connection Failure', { statusCode: reason, data: node.attrs }))
|
|
786
|
+
})
|
|
787
|
+
|
|
788
|
+
ws.on('CB:ib,,downgrade_webclient', () =>
|
|
789
|
+
void end(new Boom('Multi-device beta not joined', { statusCode: DisconnectReason.multideviceMismatch }))
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
ws.on('CB:ib,,offline_preview', async (node) => {
|
|
793
|
+
logger.info('Offline preview received', JSON.stringify(node))
|
|
794
|
+
await sendNode({ tag: 'ib', attrs: {}, content: [{ tag: 'offline_batch', attrs: { count: '100' } }] })
|
|
795
|
+
})
|
|
796
|
+
|
|
797
|
+
ws.on('CB:ib,,edge_routing', (node) => {
|
|
798
|
+
const edgeRoutingNode = getBinaryNodeChild(node, 'edge_routing')
|
|
799
|
+
const routingInfo = getBinaryNodeChild(edgeRoutingNode, 'routing_info')
|
|
800
|
+
if (routingInfo?.content) {
|
|
801
|
+
authState.creds.routingInfo = Buffer.from(routingInfo.content)
|
|
802
|
+
ev.emit('creds.update', authState.creds)
|
|
803
|
+
}
|
|
804
|
+
})
|
|
805
|
+
|
|
806
|
+
// ─── Buffering & offline notifications ──────────────────────────────────────
|
|
807
|
+
|
|
808
|
+
process.nextTick(() => {
|
|
809
|
+
if (creds.me?.id) { ev.buffer(); didStartBuffer = true }
|
|
810
|
+
ev.emit('connection.update', { connection: 'connecting', receivedPendingNotifications: false, qr: undefined })
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
ws.on('CB:ib,,offline', (node) => {
|
|
814
|
+
const child = getBinaryNodeChild(node, 'offline')
|
|
815
|
+
const offlineNotifs = +(child?.attrs.count || 0)
|
|
816
|
+
logger.info(`Handled ${offlineNotifs} offline messages/notifications`)
|
|
817
|
+
if (didStartBuffer) { ev.flush(); logger.trace('Flushed events for initial buffer') }
|
|
818
|
+
ev.emit('connection.update', { receivedPendingNotifications: true })
|
|
819
|
+
})
|
|
820
|
+
|
|
821
|
+
// ─── Creds sync ──────────────────────────────────────────────────────────────
|
|
822
|
+
|
|
823
|
+
ev.on('creds.update', (update) => {
|
|
824
|
+
const name = update.me?.name
|
|
825
|
+
if (creds.me?.name !== name) {
|
|
826
|
+
logger.debug({ name }, 'Updated pushName')
|
|
827
|
+
sendNode({ tag: 'presence', attrs: { name } }).catch((err) =>
|
|
828
|
+
logger.warn({ trace: err.stack }, 'Error in sending presence update on name change')
|
|
829
|
+
)
|
|
830
|
+
}
|
|
831
|
+
Object.assign(creds, update)
|
|
832
|
+
})
|
|
833
|
+
|
|
834
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
835
|
+
|
|
836
|
+
return {
|
|
837
|
+
type: 'md',
|
|
838
|
+
ws,
|
|
839
|
+
ev,
|
|
840
|
+
authState: { creds, keys },
|
|
841
|
+
signalRepository,
|
|
842
|
+
get user() { return authState.creds.me },
|
|
843
|
+
generateMessageTag,
|
|
844
|
+
query,
|
|
845
|
+
waitForMessage,
|
|
846
|
+
waitForSocketOpen,
|
|
847
|
+
sendRawMessage,
|
|
848
|
+
sendNode,
|
|
849
|
+
logout,
|
|
850
|
+
end,
|
|
851
|
+
registerSocketEndHandler,
|
|
852
|
+
onUnexpectedError,
|
|
853
|
+
uploadPreKeys,
|
|
854
|
+
uploadPreKeysToServerIfRequired,
|
|
855
|
+
digestKeyBundle,
|
|
856
|
+
rotateSignedPreKey,
|
|
857
|
+
updateServerTimeOffset,
|
|
858
|
+
sendUnifiedSession,
|
|
859
|
+
requestPairingCode,
|
|
860
|
+
wamBuffer: publicWAMBuffer,
|
|
861
|
+
waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),
|
|
862
|
+
sendWAMBuffer,
|
|
863
|
+
executeUSyncQuery,
|
|
864
|
+
onWhatsApp,
|
|
865
|
+
fetchAccountReachoutTimelock,
|
|
866
|
+
fetchNewChatMessageCap,
|
|
867
|
+
listener: (eventName) => {
|
|
868
|
+
if (typeof ev.listenerCount === 'function') return ev.listenerCount(eventName)
|
|
869
|
+
if (typeof ev.listener === 'function') return ev.listener(eventName)?.length || 0
|
|
870
|
+
return 0
|
|
871
|
+
}
|
|
872
|
+
}
|
|
1202
873
|
}
|