whalibmob 5.5.30 → 5.5.32
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/Client.js +167 -60
- package/lib/DeviceManager.js +164 -37
- package/lib/Registration.js +118 -13
- package/lib/Store.js +3 -1
- package/lib/logger.js +36 -0
- package/lib/messages/MessageSender.js +21 -24
- package/lib/noise.js +34 -4
- package/lib/signal/SignalProtocol.js +2 -2
- package/lib/signal/libsignal/curve.js +3 -1
- package/lib/signal/libsignal/queue_job.js +3 -1
- package/lib/signal/libsignal/session_builder.js +3 -1
- package/lib/signal/libsignal/session_cipher.js +3 -1
- package/lib/signal/libsignal/session_record.js +9 -9
- package/package.json +3 -2
package/lib/Client.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { dbg: _whaDbg, configureLogger: _whaConfigLogger } = require('./logger');
|
|
4
|
+
|
|
3
5
|
const EventEmitter = require('events');
|
|
4
6
|
const path = require('path');
|
|
5
7
|
const crypto = require('crypto');
|
|
@@ -120,6 +122,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
120
122
|
this._connected = false;
|
|
121
123
|
this._reconnecting = false;
|
|
122
124
|
this._reconnectTry = 0;
|
|
125
|
+
this._hasConnectedOnce = false; // true after first successful open; used to detect reconnects
|
|
123
126
|
this._phoneNumber = null;
|
|
124
127
|
this._mediaConn = null;
|
|
125
128
|
this._pendingIqs = new Map(); // id → resolve fn
|
|
@@ -136,6 +139,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
136
139
|
this._tcTokenStore = null; // TcTokenStore — loaded in init()
|
|
137
140
|
this._inFlightTcTokenIssuance = new Set(); // dedupe concurrent proactive issuePrivacyTokens per JID
|
|
138
141
|
this._inFlight463Recoveries = new Set(); // dedupe concurrent 463-triggered token issuances per JID (separate from proactive)
|
|
142
|
+
if (opts.pino !== undefined) _whaConfigLogger(opts.pino);
|
|
139
143
|
}
|
|
140
144
|
|
|
141
145
|
get store() { return this._store; }
|
|
@@ -181,6 +185,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
181
185
|
|
|
182
186
|
this._signal = SignalProtocol.fromStore(this._store, signalFile, skFile);
|
|
183
187
|
this._devMgr = new DeviceManager(this);
|
|
188
|
+
this._devMgr.attachSession(this._sessionDir, phoneNumber);
|
|
184
189
|
|
|
185
190
|
// Restore LID ↔ phone mappings persisted from previous sessions.
|
|
186
191
|
// This ensures we can route to LID JIDs even on fresh connections where no
|
|
@@ -191,7 +196,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
191
196
|
this._pnToLid.set(phone, lid);
|
|
192
197
|
this._lidToPn.set(lid, phone);
|
|
193
198
|
}
|
|
194
|
-
|
|
199
|
+
_whaDbg('[DBG] LID_RESTORED count=' + Object.keys(persistedLid).length);
|
|
195
200
|
|
|
196
201
|
await this._connectSocket();
|
|
197
202
|
return this;
|
|
@@ -209,6 +214,8 @@ class WhalibmobClient extends EventEmitter {
|
|
|
209
214
|
socket.on('close', () => this._onClose());
|
|
210
215
|
socket.on('error', err => this.emit('error', err));
|
|
211
216
|
|
|
217
|
+
// Pass reconnect attempt count into the Noise handshake ClientPayload
|
|
218
|
+
this._store.connectAttemptCount = this._reconnectTry;
|
|
212
219
|
await socket.connect();
|
|
213
220
|
return socket;
|
|
214
221
|
}
|
|
@@ -271,6 +278,32 @@ class WhalibmobClient extends EventEmitter {
|
|
|
271
278
|
|
|
272
279
|
this._requestMediaConnection();
|
|
273
280
|
this._uploadPreKeys();
|
|
281
|
+
|
|
282
|
+
// ── Reconnect: flush stale device cache + background re-usync ─────────────
|
|
283
|
+
// On first connect _hasConnectedOnce is false — nothing extra to do.
|
|
284
|
+
// On every reconnect (network drop / NAT reset / server restart) we flush the
|
|
285
|
+
// in-memory device cache because contacts may have linked or unlinked devices
|
|
286
|
+
// while we were offline. Disk files are kept for next restart warm-read;
|
|
287
|
+
// in-memory entries are cleared so the next send triggers a fresh usync IQ.
|
|
288
|
+
if (this._hasConnectedOnce && this._devMgr) {
|
|
289
|
+
const phonesInCache = Object.keys(this._devMgr._cacheSnapshot || {});
|
|
290
|
+
_whaDbg('[DBG] RECONNECT: flushing device cache (' + phonesInCache.length + ' entries) for fresh usync');
|
|
291
|
+
this._devMgr._dcFlush();
|
|
292
|
+
|
|
293
|
+
// Immediately re-usync own devices in the background (tablets / linked devices
|
|
294
|
+
// could have been added or unlinked while we were offline).
|
|
295
|
+
if (this._store && this._store.phoneNumber && this._signal) {
|
|
296
|
+
const ownPhone = this._store.phoneNumber;
|
|
297
|
+
setImmediate(() => {
|
|
298
|
+
if (!this._connected || !this._devMgr) return;
|
|
299
|
+
this._devMgr.ensureOwnDeviceSessions(ownPhone, this._signal, false)
|
|
300
|
+
.then(() => _whaDbg('[DBG] RECONNECT own-device usync done'))
|
|
301
|
+
.catch(e => _whaDbg('[DBG] RECONNECT own-device usync err: ' + e.message));
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
this._hasConnectedOnce = true;
|
|
306
|
+
|
|
274
307
|
this.emit('connected');
|
|
275
308
|
}
|
|
276
309
|
|
|
@@ -293,11 +326,11 @@ class WhalibmobClient extends EventEmitter {
|
|
|
293
326
|
if (!node) return;
|
|
294
327
|
const attrs = node.attrs || {};
|
|
295
328
|
// Debug: log the full success node structure
|
|
296
|
-
|
|
329
|
+
_whaDbg('[DBG] SUCCESS node attrs=' + JSON.stringify(attrs));
|
|
297
330
|
if (Array.isArray(node.content)) {
|
|
298
331
|
for (const child of node.content) {
|
|
299
332
|
if (child && child.description) {
|
|
300
|
-
|
|
333
|
+
_whaDbg('[DBG] SUCCESS child tag=' + child.description + ' attrs=' + JSON.stringify(child.attrs || {}));
|
|
301
334
|
}
|
|
302
335
|
}
|
|
303
336
|
}
|
|
@@ -331,7 +364,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
331
364
|
_sendActiveIq() {
|
|
332
365
|
if (!this._socket) return;
|
|
333
366
|
const id = this._genMsgId();
|
|
334
|
-
|
|
367
|
+
_whaDbg('[DBG] SEND active IQ id=' + id);
|
|
335
368
|
this._socket.sendNode(new BinaryNode('iq', {
|
|
336
369
|
id,
|
|
337
370
|
to: 's.whatsapp.net',
|
|
@@ -391,7 +424,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
391
424
|
for (const type of dirtyTypes) {
|
|
392
425
|
const mapped = COLLECTION_MAP[type];
|
|
393
426
|
if (!mapped) {
|
|
394
|
-
|
|
427
|
+
_whaDbg('[DBG] ignoring unknown dirty type: ' + type);
|
|
395
428
|
continue;
|
|
396
429
|
}
|
|
397
430
|
for (const c of mapped) collections.add(c);
|
|
@@ -409,7 +442,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
409
442
|
}, null);
|
|
410
443
|
});
|
|
411
444
|
|
|
412
|
-
|
|
445
|
+
_whaDbg('[DBG] SEND appStateSync IQ id=' + id + ' collections=' + [...collections].join(','));
|
|
413
446
|
this._socket.sendNode(new BinaryNode('iq', {
|
|
414
447
|
id,
|
|
415
448
|
to: 's.whatsapp.net',
|
|
@@ -436,6 +469,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
436
469
|
_onClose() {
|
|
437
470
|
this._connected = false;
|
|
438
471
|
this._stopTimers();
|
|
472
|
+
this._socket = null; // clear dead reference — prevents accidental sends before reconnect
|
|
439
473
|
this.emit('disconnected');
|
|
440
474
|
|
|
441
475
|
// Reject all pending IQs / acks
|
|
@@ -469,11 +503,27 @@ class WhalibmobClient extends EventEmitter {
|
|
|
469
503
|
}, [new BinaryNode('ping', {}, null)]));
|
|
470
504
|
}
|
|
471
505
|
}, KEEPALIVE_INTERVAL);
|
|
506
|
+
|
|
507
|
+
// Watchdog: if no data arrived from WhatsApp in 2× keepalive window,
|
|
508
|
+
// the connection is dead but TCP hasn't emitted 'close' (common with NAT drops).
|
|
509
|
+
// Force-destroy the socket so _onTcpClose fires and reconnection kicks in.
|
|
510
|
+
const WATCHDOG_INTERVAL = KEEPALIVE_INTERVAL * 2 + 5000;
|
|
511
|
+
this._watchdogTimer = setInterval(() => {
|
|
512
|
+
if (!this._socket || !this._connected) return;
|
|
513
|
+
const noiseSocket = this._socket;
|
|
514
|
+
const lastRx = noiseSocket._lastRxAt || 0;
|
|
515
|
+
const silentMs = Date.now() - lastRx;
|
|
516
|
+
if (silentMs > WATCHDOG_INTERVAL) {
|
|
517
|
+
_whaDbg('[DBG] WATCHDOG: no data for ' + Math.round(silentMs / 1000) + 's — force-closing dead connection');
|
|
518
|
+
try { noiseSocket.close(); } catch (_) {}
|
|
519
|
+
}
|
|
520
|
+
}, KEEPALIVE_INTERVAL);
|
|
472
521
|
}
|
|
473
522
|
|
|
474
523
|
_stopTimers() {
|
|
475
|
-
if (this._pingTimer)
|
|
476
|
-
if (this._keepTimer)
|
|
524
|
+
if (this._pingTimer) { clearInterval(this._pingTimer); this._pingTimer = null; }
|
|
525
|
+
if (this._keepTimer) { clearInterval(this._keepTimer); this._keepTimer = null; }
|
|
526
|
+
if (this._watchdogTimer) { clearInterval(this._watchdogTimer); this._watchdogTimer = null; }
|
|
477
527
|
}
|
|
478
528
|
|
|
479
529
|
// ─── Node dispatch ────────────────────────────────────────────────────────
|
|
@@ -482,9 +532,9 @@ class WhalibmobClient extends EventEmitter {
|
|
|
482
532
|
if (!node || !node.description) return;
|
|
483
533
|
const tag = node.description;
|
|
484
534
|
// Debug: log every node received
|
|
485
|
-
|
|
535
|
+
_whaDbg('[DBG] _onNode tag=' + tag + ' attrs=' + JSON.stringify(node.attrs || {}));
|
|
486
536
|
if (tag === 'iq' && node.attrs && node.attrs.type === 'error') {
|
|
487
|
-
try {
|
|
537
|
+
try { _whaDbg('[DBG] IQ_ERROR content=' + JSON.stringify(node.content)); } catch(_) {}
|
|
488
538
|
}
|
|
489
539
|
|
|
490
540
|
if (tag === 'iq') this._handleIq(node);
|
|
@@ -554,11 +604,11 @@ class WhalibmobClient extends EventEmitter {
|
|
|
554
604
|
}
|
|
555
605
|
}
|
|
556
606
|
}
|
|
557
|
-
|
|
607
|
+
_whaDbg('[DBG] _handleMessage from=' + from + ' participant=' + participant + ' senderPn=' + senderPn + ' id=' + id);
|
|
558
608
|
if (Array.isArray(node.content)) {
|
|
559
609
|
for (const c of node.content) {
|
|
560
610
|
if (c && c.description) {
|
|
561
|
-
|
|
611
|
+
_whaDbg('[DBG] MSG_CHILD tag=' + c.description + ' attrs=' + JSON.stringify(c.attrs || {}) + ' contentLen=' + (Buffer.isBuffer(c.content) ? c.content.length : (typeof c.content === 'string' ? c.content.length : 0)));
|
|
562
612
|
}
|
|
563
613
|
}
|
|
564
614
|
}
|
|
@@ -632,12 +682,12 @@ class WhalibmobClient extends EventEmitter {
|
|
|
632
682
|
const sigJid = senderPn
|
|
633
683
|
|| ((participant && participant !== from) ? participant : from);
|
|
634
684
|
|
|
635
|
-
|
|
685
|
+
_whaDbg('[DBG] DM_DECRYPT id=' + id + ' type=' + encType + ' sigJid=' + sigJid + ' cipherLen=' + cipherBuf.length);
|
|
636
686
|
|
|
637
687
|
this._signal.decrypt(sigJid, encType, cipherBuf)
|
|
638
688
|
.then(plaintext => {
|
|
639
689
|
const decoded = this._decodeMsg(plaintext);
|
|
640
|
-
|
|
690
|
+
_whaDbg('[DBG] DM_DECODED id=' + id + ' type=' + decoded.type + ' ptLen=' + (plaintext ? plaintext.length : 0) + ' hasSKDM=' + !!(decoded.skdm || decoded.type === 'senderKeyDistribution'));
|
|
641
691
|
|
|
642
692
|
// Process embedded SKDM when bundled with a real message (WhatsApp MD always does this
|
|
643
693
|
// on the first message of a new session, even for 1-on-1 DMs).
|
|
@@ -645,9 +695,9 @@ class WhalibmobClient extends EventEmitter {
|
|
|
645
695
|
if (decoded.skdm && decoded.skdm.axolotlBytes && decoded.skdm.axolotlBytes[0] === 0x33) {
|
|
646
696
|
try {
|
|
647
697
|
this._signal.processSKDM(decoded.skdm.groupId, sigJid, decoded.skdm.axolotlBytes);
|
|
648
|
-
|
|
698
|
+
_whaDbg('[DBG] SKDM_PROCESSED groupId=' + decoded.skdm.groupId + ' sender=' + sigJid);
|
|
649
699
|
} catch (e) {
|
|
650
|
-
|
|
700
|
+
_whaDbg('[DBG] SKDM_ERR ' + e.message);
|
|
651
701
|
}
|
|
652
702
|
}
|
|
653
703
|
|
|
@@ -656,9 +706,9 @@ class WhalibmobClient extends EventEmitter {
|
|
|
656
706
|
if (decoded.axolotlBytes && decoded.axolotlBytes[0] === 0x33) {
|
|
657
707
|
try {
|
|
658
708
|
this._signal.processSKDM(decoded.groupId, sigJid, decoded.axolotlBytes);
|
|
659
|
-
|
|
709
|
+
_whaDbg('[DBG] SKDM_ONLY_PROCESSED groupId=' + decoded.groupId + ' sender=' + sigJid);
|
|
660
710
|
} catch (e) {
|
|
661
|
-
|
|
711
|
+
_whaDbg('[DBG] SKDM_ONLY_ERR ' + e.message);
|
|
662
712
|
}
|
|
663
713
|
}
|
|
664
714
|
this._sendReadReceipt(id, fromRaw, partRaw);
|
|
@@ -666,7 +716,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
666
716
|
}
|
|
667
717
|
|
|
668
718
|
// Protocol messages (revoke, ephemeral, etc.) — silent ACK
|
|
669
|
-
if (decoded.type === 'protocol') {
|
|
719
|
+
if (decoded.type === 'protocol') { _whaDbg('[DBG] DM_FILTER proto id=' + id); return; }
|
|
670
720
|
|
|
671
721
|
this.emit('message', { id, from, participant, ts, decoded, node });
|
|
672
722
|
this._sendReadReceipt(id, fromRaw, partRaw);
|
|
@@ -679,7 +729,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
679
729
|
}
|
|
680
730
|
})
|
|
681
731
|
.catch(err => {
|
|
682
|
-
|
|
732
|
+
_whaDbg('[DBG] DM_ERR id=' + id + ' err=' + (err && err.message));
|
|
683
733
|
this.emit('decrypt_error', { id, from, participant, err });
|
|
684
734
|
this._sendRetryRequest(id, node);
|
|
685
735
|
});
|
|
@@ -785,7 +835,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
785
835
|
const msgId = String(attrs.id || '');
|
|
786
836
|
const cached = this._sentMsgCache && this._sentMsgCache.get(msgId);
|
|
787
837
|
if (!cached || !this._sender) {
|
|
788
|
-
|
|
838
|
+
_whaDbg('[DBG] RETRY_RECV msgId=' + msgId + ' — no cached plaintext, skipping resend\n');
|
|
789
839
|
return;
|
|
790
840
|
}
|
|
791
841
|
|
|
@@ -793,7 +843,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
793
843
|
// Extract bare phone number: "40756469325@s.whatsapp.net" → "40756469325"
|
|
794
844
|
const recipientPhone = fromStr.split('@')[0].split('.')[0];
|
|
795
845
|
|
|
796
|
-
|
|
846
|
+
_whaDbg('[DBG] RETRY_RECV msgId=' + msgId + ' from=' + fromStr + ' — clearing session + resending\n');
|
|
797
847
|
|
|
798
848
|
// Delete all Signal sessions for the recipient's devices so next send creates fresh pkmsg
|
|
799
849
|
const sigStore = this._signal && this._signal.store;
|
|
@@ -801,7 +851,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
801
851
|
const sessions = sigStore._sessions;
|
|
802
852
|
Object.keys(sessions).forEach(addr => {
|
|
803
853
|
if (addr.startsWith(recipientPhone + '.')) {
|
|
804
|
-
|
|
854
|
+
_whaDbg('[DBG] RETRY deleting session for ' + addr);
|
|
805
855
|
delete sessions[addr];
|
|
806
856
|
}
|
|
807
857
|
});
|
|
@@ -816,8 +866,8 @@ class WhalibmobClient extends EventEmitter {
|
|
|
816
866
|
this._sender._sendDMMessage(
|
|
817
867
|
cached.toJid, cached.msgId, cached.plaintext, cached.mediaType, cached.options
|
|
818
868
|
)
|
|
819
|
-
.then(r =>
|
|
820
|
-
.catch(e =>
|
|
869
|
+
.then(r => _whaDbg('[DBG] RETRY_RESEND ok id=' + r.id + '\n'))
|
|
870
|
+
.catch(e => _whaDbg('[DBG] RETRY_RESEND_ERR: ' + e.message));
|
|
821
871
|
}
|
|
822
872
|
|
|
823
873
|
_handleAck(node) {
|
|
@@ -886,7 +936,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
886
936
|
// Assign a stable, unique prekey index per msgId on first retry
|
|
887
937
|
let pkIdx = existing ? existing.pkIdx : -1;
|
|
888
938
|
if (pkIdx < 0 && this._signal) {
|
|
889
|
-
const allKeys = this._signal.getPreKeysForUpload(
|
|
939
|
+
const allKeys = this._signal.getPreKeysForUpload(800);
|
|
890
940
|
pkIdx = this._retryPreKeyIdx++ % Math.max(1, allKeys.length);
|
|
891
941
|
}
|
|
892
942
|
this._retryPending.set(msgId, { node: origNode, count, pkIdx });
|
|
@@ -907,7 +957,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
907
957
|
try {
|
|
908
958
|
const spk = this._signal.getSignedPreKeyForUpload();
|
|
909
959
|
const identKey = this._signal.getIdentityKey();
|
|
910
|
-
const allPreKeys = this._signal.getPreKeysForUpload(
|
|
960
|
+
const allPreKeys = this._signal.getPreKeysForUpload(800);
|
|
911
961
|
const pk = allPreKeys[pkIdx] || allPreKeys[0];
|
|
912
962
|
|
|
913
963
|
if (spk && identKey && pk) {
|
|
@@ -929,15 +979,15 @@ class WhalibmobClient extends EventEmitter {
|
|
|
929
979
|
const advBytes = buildOrGetAdvIdentity(this._store);
|
|
930
980
|
if (advBytes && advBytes.length > 0) {
|
|
931
981
|
keysChildren.push(new BinaryNode('device-identity', {}, advBytes));
|
|
932
|
-
|
|
982
|
+
_whaDbg('[DBG] RETRY #' + count + ' for ' + msgId + ' — preKeyId=' + pk.keyId + ' +device-identity(' + advBytes.length + 'b)\n');
|
|
933
983
|
} else {
|
|
934
|
-
|
|
984
|
+
_whaDbg('[DBG] RETRY #' + count + ' for ' + msgId + ' — preKeyId=' + pk.keyId + ' (no advIdentity)\n');
|
|
935
985
|
}
|
|
936
986
|
|
|
937
987
|
children.push(new BinaryNode('keys', {}, keysChildren));
|
|
938
988
|
}
|
|
939
989
|
} catch (e) {
|
|
940
|
-
|
|
990
|
+
_whaDbg('[DBG] RETRY prekey bundle error: ' + e.message);
|
|
941
991
|
}
|
|
942
992
|
}
|
|
943
993
|
|
|
@@ -952,7 +1002,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
952
1002
|
if (origAttrs.recipient) receiptAttrs.recipient = origAttrs.recipient;
|
|
953
1003
|
if (origAttrs.participant) receiptAttrs.participant = origAttrs.participant;
|
|
954
1004
|
|
|
955
|
-
|
|
1005
|
+
_whaDbg('[DBG] RETRY #' + count + ' receipt to=' + JSON.stringify(fromJid) + ' participant=' + JSON.stringify(origAttrs.participant || null));
|
|
956
1006
|
this._socket.sendNode(new BinaryNode('receipt', receiptAttrs, children));
|
|
957
1007
|
}
|
|
958
1008
|
|
|
@@ -986,6 +1036,50 @@ class WhalibmobClient extends EventEmitter {
|
|
|
986
1036
|
this._handlePrivacyTokenNotification(node);
|
|
987
1037
|
}
|
|
988
1038
|
|
|
1039
|
+
if (type === 'devices') {
|
|
1040
|
+
// Server push: a contact's linked device list changed (they linked or
|
|
1041
|
+
// unlinked a tablet, desktop, etc.). Invalidate that phone's cache entry
|
|
1042
|
+
// so the next send triggers a fresh usync IQ and reaches all their devices.
|
|
1043
|
+
const fromJid = attrs.from || '';
|
|
1044
|
+
const fromPhone = fromJid.split('@')[0].split(':')[0];
|
|
1045
|
+
if (fromPhone && this._devMgr) {
|
|
1046
|
+
this._devMgr._dcDel([fromPhone]);
|
|
1047
|
+
_whaDbg('[DBG] NOTIF_DEVICES invalidated cache for ' + fromPhone);
|
|
1048
|
+
// Also process any inline <devices> list the server included
|
|
1049
|
+
const devicesNode = findChildDeep(node, 'devices');
|
|
1050
|
+
if (devicesNode) this._processDeviceUpdate(devicesNode);
|
|
1051
|
+
// Background re-usync so the cache is warm before the next send
|
|
1052
|
+
if (this._signal) {
|
|
1053
|
+
setImmediate(() => {
|
|
1054
|
+
if (!this._connected || !this._devMgr) return;
|
|
1055
|
+
this._devMgr.bulkEnsureSessions([fromPhone], this._signal, false)
|
|
1056
|
+
.then(() => _whaDbg('[DBG] NOTIF_DEVICES bg-usync done for ' + fromPhone))
|
|
1057
|
+
.catch(e => _whaDbg('[DBG] NOTIF_DEVICES bg-usync err: ' + e.message));
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (type === 'identity') {
|
|
1064
|
+
// Server push: a contact re-registered WhatsApp (new identity key / new phone).
|
|
1065
|
+
// Their old Signal sessions are no longer valid — clear them and the device
|
|
1066
|
+
// cache so the next send builds a fresh pkmsg session from scratch.
|
|
1067
|
+
const fromJid = attrs.from || '';
|
|
1068
|
+
const fromPhone = fromJid.split('@')[0].split(':')[0];
|
|
1069
|
+
if (fromPhone && this._devMgr) {
|
|
1070
|
+
this._devMgr._dcDel([fromPhone]);
|
|
1071
|
+
_whaDbg('[DBG] NOTIF_IDENTITY flushed device cache for re-registered ' + fromPhone);
|
|
1072
|
+
}
|
|
1073
|
+
if (fromPhone && this._signal && this._signal.store && this._signal.store._sessions) {
|
|
1074
|
+
const sessions = this._signal.store._sessions;
|
|
1075
|
+
let cleared = 0;
|
|
1076
|
+
for (const addr of Object.keys(sessions)) {
|
|
1077
|
+
if (addr.startsWith(fromPhone + '.')) { delete sessions[addr]; cleared++; }
|
|
1078
|
+
}
|
|
1079
|
+
_whaDbg('[DBG] NOTIF_IDENTITY cleared ' + cleared + ' Signal sessions for ' + fromPhone);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
989
1083
|
// Ack the notification
|
|
990
1084
|
if (this._socket && this._connected) {
|
|
991
1085
|
this._socket.sendNode(new BinaryNode('ack', {
|
|
@@ -998,16 +1092,29 @@ class WhalibmobClient extends EventEmitter {
|
|
|
998
1092
|
}
|
|
999
1093
|
|
|
1000
1094
|
_processDeviceUpdate(devicesNode) {
|
|
1001
|
-
|
|
1095
|
+
// Called from _handleNotification(type='account_sync') and type='devices'.
|
|
1096
|
+
// Rebuilds the device cache for the affected phones from the server-provided list.
|
|
1097
|
+
// Previous bug: called Set.add() on cache entries that are number[] arrays → silently failed.
|
|
1098
|
+
if (!Array.isArray(devicesNode.content) || !this._devMgr) return;
|
|
1099
|
+
|
|
1100
|
+
// Group all device IDs per phone from the notification
|
|
1101
|
+
const phoneDevices = new Map(); // phone → number[]
|
|
1002
1102
|
for (const deviceNode of devicesNode.content) {
|
|
1003
1103
|
if (!deviceNode || deviceNode.description !== 'device') continue;
|
|
1004
1104
|
const jid = deviceNode.attrs && deviceNode.attrs.jid;
|
|
1005
|
-
if (jid
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1105
|
+
if (!jid) continue;
|
|
1106
|
+
const phone = String(jid).split('@')[0].split(':')[0];
|
|
1107
|
+
const device = parseInt((String(jid).split(':')[1] || '0').split('@')[0], 10);
|
|
1108
|
+
if (!phoneDevices.has(phone)) phoneDevices.set(phone, []);
|
|
1109
|
+
const ids = phoneDevices.get(phone);
|
|
1110
|
+
if (!ids.includes(device)) ids.push(device);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// Replace cache entries atomically per phone (server list is authoritative)
|
|
1114
|
+
for (const [phone, ids] of phoneDevices) {
|
|
1115
|
+
this._devMgr._dcDel([phone]); // evict stale entry
|
|
1116
|
+
for (const id of ids) this._devMgr._dcAdd(phone, id);
|
|
1117
|
+
_whaDbg('[DBG] DEV_UPDATE phone=' + phone + ' ids=[' + ids.join(',') + ']');
|
|
1011
1118
|
}
|
|
1012
1119
|
}
|
|
1013
1120
|
|
|
@@ -1114,7 +1221,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1114
1221
|
_requestMediaConnection() {
|
|
1115
1222
|
if (!this._socket || !this._connected) return;
|
|
1116
1223
|
const mcId = this._genMsgId();
|
|
1117
|
-
|
|
1224
|
+
_whaDbg('[DBG] SEND media_conn IQ id=' + mcId);
|
|
1118
1225
|
this._socket.sendNode(new BinaryNode('iq', {
|
|
1119
1226
|
id: mcId,
|
|
1120
1227
|
to: 's.whatsapp.net',
|
|
@@ -1146,7 +1253,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1146
1253
|
_uploadPreKeys() {
|
|
1147
1254
|
if (!this._signal || !this._socket || !this._connected) return;
|
|
1148
1255
|
|
|
1149
|
-
const preKeys = this._signal.getPreKeysForUpload(
|
|
1256
|
+
const preKeys = this._signal.getPreKeysForUpload(800);
|
|
1150
1257
|
const spk = this._signal.getSignedPreKeyForUpload();
|
|
1151
1258
|
const identKey = this._signal.getIdentityKey();
|
|
1152
1259
|
if (!preKeys.length || !spk) return;
|
|
@@ -1163,7 +1270,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1163
1270
|
]);
|
|
1164
1271
|
|
|
1165
1272
|
const pkId = this._genMsgId();
|
|
1166
|
-
|
|
1273
|
+
_whaDbg('[DBG] SEND uploadPreKeys IQ id=' + pkId);
|
|
1167
1274
|
this._socket.sendNode(new BinaryNode('iq', {
|
|
1168
1275
|
id: pkId,
|
|
1169
1276
|
to: 's.whatsapp.net',
|
|
@@ -1413,7 +1520,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1413
1520
|
})
|
|
1414
1521
|
])
|
|
1415
1522
|
]);
|
|
1416
|
-
|
|
1523
|
+
_whaDbg('[DBG] ISSUE_PRIVACY_TOKENS jid=' + normalizedJid + ' t=' + timestamp + ' iq_id=' + id);
|
|
1417
1524
|
return this._sendIq(node);
|
|
1418
1525
|
}
|
|
1419
1526
|
|
|
@@ -1437,23 +1544,23 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1437
1544
|
|
|
1438
1545
|
// ── Debug: dump result structure so we can trace server response ─────────
|
|
1439
1546
|
if (!result) {
|
|
1440
|
-
|
|
1547
|
+
_whaDbg('[DBG] STORE_TCTOKEN result=NULL (IQ timeout or not matched) fallback=' + fallbackJid);
|
|
1441
1548
|
} else {
|
|
1442
1549
|
const contentLen = Array.isArray(result.content) ? result.content.length
|
|
1443
1550
|
: Buffer.isBuffer(result.content) ? result.content.length
|
|
1444
1551
|
: 0;
|
|
1445
|
-
|
|
1552
|
+
_whaDbg('[DBG] STORE_TCTOKEN result type=' + (result.attrs && result.attrs.type) +
|
|
1446
1553
|
' id=' + (result.attrs && result.attrs.id) +
|
|
1447
|
-
' contentLen=' + contentLen
|
|
1554
|
+
' contentLen=' + contentLen);
|
|
1448
1555
|
if (Array.isArray(result.content)) {
|
|
1449
1556
|
result.content.forEach((c, i) => {
|
|
1450
1557
|
if (c && c.description) {
|
|
1451
1558
|
const childContent = Array.isArray(c.content) ? c.content.length + ' children'
|
|
1452
1559
|
: Buffer.isBuffer(c.content) ? c.content.length + 'B'
|
|
1453
1560
|
: String(c.content);
|
|
1454
|
-
|
|
1561
|
+
_whaDbg('[DBG] STORE_TCTOKEN child[' + i + '] tag=' + c.description +
|
|
1455
1562
|
' attrs=' + JSON.stringify(c.attrs || {}) +
|
|
1456
|
-
' content=' + childContent
|
|
1563
|
+
' content=' + childContent);
|
|
1457
1564
|
}
|
|
1458
1565
|
});
|
|
1459
1566
|
}
|
|
@@ -1486,13 +1593,13 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1486
1593
|
if (bytes && bytes.length) {
|
|
1487
1594
|
this._tcTokenStore.setToken(jid, bytes, t);
|
|
1488
1595
|
tokenStoredCount++;
|
|
1489
|
-
|
|
1596
|
+
_whaDbg('[DBG] TCTOKEN_STORED jid=' + jid + ' t=' + t + ' len=' + bytes.length);
|
|
1490
1597
|
} else {
|
|
1491
|
-
|
|
1598
|
+
_whaDbg('[DBG] STORE_TCTOKEN token node has no bytes jid=' + jid);
|
|
1492
1599
|
}
|
|
1493
1600
|
}
|
|
1494
1601
|
} catch (e) {
|
|
1495
|
-
|
|
1602
|
+
_whaDbg('[DBG] STORE_TCTOKEN parse error: ' + e.message);
|
|
1496
1603
|
}
|
|
1497
1604
|
}
|
|
1498
1605
|
|
|
@@ -1505,8 +1612,8 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1505
1612
|
if (saveSenderTs && issueTs != null) {
|
|
1506
1613
|
try {
|
|
1507
1614
|
this._tcTokenStore.setSenderTimestamp(fallbackJid, issueTs);
|
|
1508
|
-
|
|
1509
|
-
' tokenBytes=' + tokenStoredCount
|
|
1615
|
+
_whaDbg('[DBG] SENDER_TS_SAVED jid=' + fallbackJid + ' ts=' + issueTs +
|
|
1616
|
+
' tokenBytes=' + tokenStoredCount);
|
|
1510
1617
|
} catch (_) {}
|
|
1511
1618
|
}
|
|
1512
1619
|
}
|
|
@@ -1533,14 +1640,14 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1533
1640
|
const { normalizeJidForTcToken } = require('./messages/TcTokenStore');
|
|
1534
1641
|
|
|
1535
1642
|
const attrs = node.attrs || {};
|
|
1536
|
-
|
|
1537
|
-
' sender_lid=' + String(attrs.sender_lid || '')
|
|
1643
|
+
_whaDbg('[DBG] PRIVACY_TOKEN_NOTIF_ENTER from=' + String(attrs.from || '') +
|
|
1644
|
+
' sender_lid=' + String(attrs.sender_lid || ''));
|
|
1538
1645
|
|
|
1539
1646
|
// ── 1. Require <tokens> wrapper (matches Baileys' getBinaryNodeChild check) ──
|
|
1540
1647
|
const content = Array.isArray(node.content) ? node.content : [];
|
|
1541
1648
|
const tokensNode = content.find(n => n && n.description === 'tokens');
|
|
1542
1649
|
if (!tokensNode) {
|
|
1543
|
-
|
|
1650
|
+
_whaDbg('[DBG] PRIVACY_TOKEN_NOTIF no <tokens> wrapper — abort\n');
|
|
1544
1651
|
return;
|
|
1545
1652
|
}
|
|
1546
1653
|
|
|
@@ -1569,7 +1676,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1569
1676
|
storageJid = normalizeJidForTcToken(rawFrom);
|
|
1570
1677
|
}
|
|
1571
1678
|
|
|
1572
|
-
|
|
1679
|
+
_whaDbg('[DBG] PRIVACY_TOKEN_NOTIF storageJid=' + storageJid);
|
|
1573
1680
|
|
|
1574
1681
|
// ── 3. Parse <token> children and store bytes (no senderTimestamp update) ──
|
|
1575
1682
|
// Reuse _storeTcTokenFromIqResult with the full notification node so that its
|
|
@@ -1606,15 +1713,15 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1606
1713
|
return;
|
|
1607
1714
|
}
|
|
1608
1715
|
|
|
1609
|
-
|
|
1610
|
-
' prevSenderTs=' + senderTs
|
|
1716
|
+
_whaDbg('[DBG] REISSUE_TC_TOKEN_AFTER_IDENTITY_CHANGE jid=' + tcJid +
|
|
1717
|
+
' prevSenderTs=' + senderTs);
|
|
1611
1718
|
|
|
1612
1719
|
const issueTs = Math.floor(Date.now() / 1000);
|
|
1613
1720
|
const result = await this._issuePrivacyTokens(tcJid, issueTs);
|
|
1614
1721
|
this._storeTcTokenFromIqResult(result, tcJid, issueTs);
|
|
1615
1722
|
} catch (e) {
|
|
1616
|
-
|
|
1617
|
-
' err=' + (e && e.message)
|
|
1723
|
+
_whaDbg('[DBG] REISSUE_TC_TOKEN_ERR from=' + from +
|
|
1724
|
+
' err=' + (e && e.message));
|
|
1618
1725
|
}
|
|
1619
1726
|
})();
|
|
1620
1727
|
}
|