whalibmob 5.5.30 → 5.5.31
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 +76 -53
- package/lib/DeviceManager.js +164 -37
- package/lib/Registration.js +3 -1
- package/lib/Store.js +3 -1
- package/lib/logger.js +36 -0
- package/lib/messages/MessageSender.js +21 -24
- package/lib/noise.js +32 -2
- 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');
|
|
@@ -136,6 +138,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
136
138
|
this._tcTokenStore = null; // TcTokenStore — loaded in init()
|
|
137
139
|
this._inFlightTcTokenIssuance = new Set(); // dedupe concurrent proactive issuePrivacyTokens per JID
|
|
138
140
|
this._inFlight463Recoveries = new Set(); // dedupe concurrent 463-triggered token issuances per JID (separate from proactive)
|
|
141
|
+
if (opts.pino !== undefined) _whaConfigLogger(opts.pino);
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
get store() { return this._store; }
|
|
@@ -181,6 +184,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
181
184
|
|
|
182
185
|
this._signal = SignalProtocol.fromStore(this._store, signalFile, skFile);
|
|
183
186
|
this._devMgr = new DeviceManager(this);
|
|
187
|
+
this._devMgr.attachSession(this._sessionDir, phoneNumber);
|
|
184
188
|
|
|
185
189
|
// Restore LID ↔ phone mappings persisted from previous sessions.
|
|
186
190
|
// This ensures we can route to LID JIDs even on fresh connections where no
|
|
@@ -191,7 +195,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
191
195
|
this._pnToLid.set(phone, lid);
|
|
192
196
|
this._lidToPn.set(lid, phone);
|
|
193
197
|
}
|
|
194
|
-
|
|
198
|
+
_whaDbg('[DBG] LID_RESTORED count=' + Object.keys(persistedLid).length);
|
|
195
199
|
|
|
196
200
|
await this._connectSocket();
|
|
197
201
|
return this;
|
|
@@ -209,6 +213,8 @@ class WhalibmobClient extends EventEmitter {
|
|
|
209
213
|
socket.on('close', () => this._onClose());
|
|
210
214
|
socket.on('error', err => this.emit('error', err));
|
|
211
215
|
|
|
216
|
+
// Pass reconnect attempt count into the Noise handshake ClientPayload
|
|
217
|
+
this._store.connectAttemptCount = this._reconnectTry;
|
|
212
218
|
await socket.connect();
|
|
213
219
|
return socket;
|
|
214
220
|
}
|
|
@@ -293,11 +299,11 @@ class WhalibmobClient extends EventEmitter {
|
|
|
293
299
|
if (!node) return;
|
|
294
300
|
const attrs = node.attrs || {};
|
|
295
301
|
// Debug: log the full success node structure
|
|
296
|
-
|
|
302
|
+
_whaDbg('[DBG] SUCCESS node attrs=' + JSON.stringify(attrs));
|
|
297
303
|
if (Array.isArray(node.content)) {
|
|
298
304
|
for (const child of node.content) {
|
|
299
305
|
if (child && child.description) {
|
|
300
|
-
|
|
306
|
+
_whaDbg('[DBG] SUCCESS child tag=' + child.description + ' attrs=' + JSON.stringify(child.attrs || {}));
|
|
301
307
|
}
|
|
302
308
|
}
|
|
303
309
|
}
|
|
@@ -331,7 +337,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
331
337
|
_sendActiveIq() {
|
|
332
338
|
if (!this._socket) return;
|
|
333
339
|
const id = this._genMsgId();
|
|
334
|
-
|
|
340
|
+
_whaDbg('[DBG] SEND active IQ id=' + id);
|
|
335
341
|
this._socket.sendNode(new BinaryNode('iq', {
|
|
336
342
|
id,
|
|
337
343
|
to: 's.whatsapp.net',
|
|
@@ -391,7 +397,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
391
397
|
for (const type of dirtyTypes) {
|
|
392
398
|
const mapped = COLLECTION_MAP[type];
|
|
393
399
|
if (!mapped) {
|
|
394
|
-
|
|
400
|
+
_whaDbg('[DBG] ignoring unknown dirty type: ' + type);
|
|
395
401
|
continue;
|
|
396
402
|
}
|
|
397
403
|
for (const c of mapped) collections.add(c);
|
|
@@ -409,7 +415,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
409
415
|
}, null);
|
|
410
416
|
});
|
|
411
417
|
|
|
412
|
-
|
|
418
|
+
_whaDbg('[DBG] SEND appStateSync IQ id=' + id + ' collections=' + [...collections].join(','));
|
|
413
419
|
this._socket.sendNode(new BinaryNode('iq', {
|
|
414
420
|
id,
|
|
415
421
|
to: 's.whatsapp.net',
|
|
@@ -436,6 +442,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
436
442
|
_onClose() {
|
|
437
443
|
this._connected = false;
|
|
438
444
|
this._stopTimers();
|
|
445
|
+
this._socket = null; // clear dead reference — prevents accidental sends before reconnect
|
|
439
446
|
this.emit('disconnected');
|
|
440
447
|
|
|
441
448
|
// Reject all pending IQs / acks
|
|
@@ -469,11 +476,27 @@ class WhalibmobClient extends EventEmitter {
|
|
|
469
476
|
}, [new BinaryNode('ping', {}, null)]));
|
|
470
477
|
}
|
|
471
478
|
}, KEEPALIVE_INTERVAL);
|
|
479
|
+
|
|
480
|
+
// Watchdog: if no data arrived from WhatsApp in 2× keepalive window,
|
|
481
|
+
// the connection is dead but TCP hasn't emitted 'close' (common with NAT drops).
|
|
482
|
+
// Force-destroy the socket so _onTcpClose fires and reconnection kicks in.
|
|
483
|
+
const WATCHDOG_INTERVAL = KEEPALIVE_INTERVAL * 2 + 5000;
|
|
484
|
+
this._watchdogTimer = setInterval(() => {
|
|
485
|
+
if (!this._socket || !this._connected) return;
|
|
486
|
+
const noiseSocket = this._socket;
|
|
487
|
+
const lastRx = noiseSocket._lastRxAt || 0;
|
|
488
|
+
const silentMs = Date.now() - lastRx;
|
|
489
|
+
if (silentMs > WATCHDOG_INTERVAL) {
|
|
490
|
+
_whaDbg('[DBG] WATCHDOG: no data for ' + Math.round(silentMs / 1000) + 's — force-closing dead connection');
|
|
491
|
+
try { noiseSocket.close(); } catch (_) {}
|
|
492
|
+
}
|
|
493
|
+
}, KEEPALIVE_INTERVAL);
|
|
472
494
|
}
|
|
473
495
|
|
|
474
496
|
_stopTimers() {
|
|
475
|
-
if (this._pingTimer)
|
|
476
|
-
if (this._keepTimer)
|
|
497
|
+
if (this._pingTimer) { clearInterval(this._pingTimer); this._pingTimer = null; }
|
|
498
|
+
if (this._keepTimer) { clearInterval(this._keepTimer); this._keepTimer = null; }
|
|
499
|
+
if (this._watchdogTimer) { clearInterval(this._watchdogTimer); this._watchdogTimer = null; }
|
|
477
500
|
}
|
|
478
501
|
|
|
479
502
|
// ─── Node dispatch ────────────────────────────────────────────────────────
|
|
@@ -482,9 +505,9 @@ class WhalibmobClient extends EventEmitter {
|
|
|
482
505
|
if (!node || !node.description) return;
|
|
483
506
|
const tag = node.description;
|
|
484
507
|
// Debug: log every node received
|
|
485
|
-
|
|
508
|
+
_whaDbg('[DBG] _onNode tag=' + tag + ' attrs=' + JSON.stringify(node.attrs || {}));
|
|
486
509
|
if (tag === 'iq' && node.attrs && node.attrs.type === 'error') {
|
|
487
|
-
try {
|
|
510
|
+
try { _whaDbg('[DBG] IQ_ERROR content=' + JSON.stringify(node.content)); } catch(_) {}
|
|
488
511
|
}
|
|
489
512
|
|
|
490
513
|
if (tag === 'iq') this._handleIq(node);
|
|
@@ -554,11 +577,11 @@ class WhalibmobClient extends EventEmitter {
|
|
|
554
577
|
}
|
|
555
578
|
}
|
|
556
579
|
}
|
|
557
|
-
|
|
580
|
+
_whaDbg('[DBG] _handleMessage from=' + from + ' participant=' + participant + ' senderPn=' + senderPn + ' id=' + id);
|
|
558
581
|
if (Array.isArray(node.content)) {
|
|
559
582
|
for (const c of node.content) {
|
|
560
583
|
if (c && c.description) {
|
|
561
|
-
|
|
584
|
+
_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
585
|
}
|
|
563
586
|
}
|
|
564
587
|
}
|
|
@@ -632,12 +655,12 @@ class WhalibmobClient extends EventEmitter {
|
|
|
632
655
|
const sigJid = senderPn
|
|
633
656
|
|| ((participant && participant !== from) ? participant : from);
|
|
634
657
|
|
|
635
|
-
|
|
658
|
+
_whaDbg('[DBG] DM_DECRYPT id=' + id + ' type=' + encType + ' sigJid=' + sigJid + ' cipherLen=' + cipherBuf.length);
|
|
636
659
|
|
|
637
660
|
this._signal.decrypt(sigJid, encType, cipherBuf)
|
|
638
661
|
.then(plaintext => {
|
|
639
662
|
const decoded = this._decodeMsg(plaintext);
|
|
640
|
-
|
|
663
|
+
_whaDbg('[DBG] DM_DECODED id=' + id + ' type=' + decoded.type + ' ptLen=' + (plaintext ? plaintext.length : 0) + ' hasSKDM=' + !!(decoded.skdm || decoded.type === 'senderKeyDistribution'));
|
|
641
664
|
|
|
642
665
|
// Process embedded SKDM when bundled with a real message (WhatsApp MD always does this
|
|
643
666
|
// on the first message of a new session, even for 1-on-1 DMs).
|
|
@@ -645,9 +668,9 @@ class WhalibmobClient extends EventEmitter {
|
|
|
645
668
|
if (decoded.skdm && decoded.skdm.axolotlBytes && decoded.skdm.axolotlBytes[0] === 0x33) {
|
|
646
669
|
try {
|
|
647
670
|
this._signal.processSKDM(decoded.skdm.groupId, sigJid, decoded.skdm.axolotlBytes);
|
|
648
|
-
|
|
671
|
+
_whaDbg('[DBG] SKDM_PROCESSED groupId=' + decoded.skdm.groupId + ' sender=' + sigJid);
|
|
649
672
|
} catch (e) {
|
|
650
|
-
|
|
673
|
+
_whaDbg('[DBG] SKDM_ERR ' + e.message);
|
|
651
674
|
}
|
|
652
675
|
}
|
|
653
676
|
|
|
@@ -656,9 +679,9 @@ class WhalibmobClient extends EventEmitter {
|
|
|
656
679
|
if (decoded.axolotlBytes && decoded.axolotlBytes[0] === 0x33) {
|
|
657
680
|
try {
|
|
658
681
|
this._signal.processSKDM(decoded.groupId, sigJid, decoded.axolotlBytes);
|
|
659
|
-
|
|
682
|
+
_whaDbg('[DBG] SKDM_ONLY_PROCESSED groupId=' + decoded.groupId + ' sender=' + sigJid);
|
|
660
683
|
} catch (e) {
|
|
661
|
-
|
|
684
|
+
_whaDbg('[DBG] SKDM_ONLY_ERR ' + e.message);
|
|
662
685
|
}
|
|
663
686
|
}
|
|
664
687
|
this._sendReadReceipt(id, fromRaw, partRaw);
|
|
@@ -666,7 +689,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
666
689
|
}
|
|
667
690
|
|
|
668
691
|
// Protocol messages (revoke, ephemeral, etc.) — silent ACK
|
|
669
|
-
if (decoded.type === 'protocol') {
|
|
692
|
+
if (decoded.type === 'protocol') { _whaDbg('[DBG] DM_FILTER proto id=' + id); return; }
|
|
670
693
|
|
|
671
694
|
this.emit('message', { id, from, participant, ts, decoded, node });
|
|
672
695
|
this._sendReadReceipt(id, fromRaw, partRaw);
|
|
@@ -679,7 +702,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
679
702
|
}
|
|
680
703
|
})
|
|
681
704
|
.catch(err => {
|
|
682
|
-
|
|
705
|
+
_whaDbg('[DBG] DM_ERR id=' + id + ' err=' + (err && err.message));
|
|
683
706
|
this.emit('decrypt_error', { id, from, participant, err });
|
|
684
707
|
this._sendRetryRequest(id, node);
|
|
685
708
|
});
|
|
@@ -785,7 +808,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
785
808
|
const msgId = String(attrs.id || '');
|
|
786
809
|
const cached = this._sentMsgCache && this._sentMsgCache.get(msgId);
|
|
787
810
|
if (!cached || !this._sender) {
|
|
788
|
-
|
|
811
|
+
_whaDbg('[DBG] RETRY_RECV msgId=' + msgId + ' — no cached plaintext, skipping resend\n');
|
|
789
812
|
return;
|
|
790
813
|
}
|
|
791
814
|
|
|
@@ -793,7 +816,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
793
816
|
// Extract bare phone number: "40756469325@s.whatsapp.net" → "40756469325"
|
|
794
817
|
const recipientPhone = fromStr.split('@')[0].split('.')[0];
|
|
795
818
|
|
|
796
|
-
|
|
819
|
+
_whaDbg('[DBG] RETRY_RECV msgId=' + msgId + ' from=' + fromStr + ' — clearing session + resending\n');
|
|
797
820
|
|
|
798
821
|
// Delete all Signal sessions for the recipient's devices so next send creates fresh pkmsg
|
|
799
822
|
const sigStore = this._signal && this._signal.store;
|
|
@@ -801,7 +824,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
801
824
|
const sessions = sigStore._sessions;
|
|
802
825
|
Object.keys(sessions).forEach(addr => {
|
|
803
826
|
if (addr.startsWith(recipientPhone + '.')) {
|
|
804
|
-
|
|
827
|
+
_whaDbg('[DBG] RETRY deleting session for ' + addr);
|
|
805
828
|
delete sessions[addr];
|
|
806
829
|
}
|
|
807
830
|
});
|
|
@@ -816,8 +839,8 @@ class WhalibmobClient extends EventEmitter {
|
|
|
816
839
|
this._sender._sendDMMessage(
|
|
817
840
|
cached.toJid, cached.msgId, cached.plaintext, cached.mediaType, cached.options
|
|
818
841
|
)
|
|
819
|
-
.then(r =>
|
|
820
|
-
.catch(e =>
|
|
842
|
+
.then(r => _whaDbg('[DBG] RETRY_RESEND ok id=' + r.id + '\n'))
|
|
843
|
+
.catch(e => _whaDbg('[DBG] RETRY_RESEND_ERR: ' + e.message));
|
|
821
844
|
}
|
|
822
845
|
|
|
823
846
|
_handleAck(node) {
|
|
@@ -886,7 +909,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
886
909
|
// Assign a stable, unique prekey index per msgId on first retry
|
|
887
910
|
let pkIdx = existing ? existing.pkIdx : -1;
|
|
888
911
|
if (pkIdx < 0 && this._signal) {
|
|
889
|
-
const allKeys = this._signal.getPreKeysForUpload(
|
|
912
|
+
const allKeys = this._signal.getPreKeysForUpload(800);
|
|
890
913
|
pkIdx = this._retryPreKeyIdx++ % Math.max(1, allKeys.length);
|
|
891
914
|
}
|
|
892
915
|
this._retryPending.set(msgId, { node: origNode, count, pkIdx });
|
|
@@ -907,7 +930,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
907
930
|
try {
|
|
908
931
|
const spk = this._signal.getSignedPreKeyForUpload();
|
|
909
932
|
const identKey = this._signal.getIdentityKey();
|
|
910
|
-
const allPreKeys = this._signal.getPreKeysForUpload(
|
|
933
|
+
const allPreKeys = this._signal.getPreKeysForUpload(800);
|
|
911
934
|
const pk = allPreKeys[pkIdx] || allPreKeys[0];
|
|
912
935
|
|
|
913
936
|
if (spk && identKey && pk) {
|
|
@@ -929,15 +952,15 @@ class WhalibmobClient extends EventEmitter {
|
|
|
929
952
|
const advBytes = buildOrGetAdvIdentity(this._store);
|
|
930
953
|
if (advBytes && advBytes.length > 0) {
|
|
931
954
|
keysChildren.push(new BinaryNode('device-identity', {}, advBytes));
|
|
932
|
-
|
|
955
|
+
_whaDbg('[DBG] RETRY #' + count + ' for ' + msgId + ' — preKeyId=' + pk.keyId + ' +device-identity(' + advBytes.length + 'b)\n');
|
|
933
956
|
} else {
|
|
934
|
-
|
|
957
|
+
_whaDbg('[DBG] RETRY #' + count + ' for ' + msgId + ' — preKeyId=' + pk.keyId + ' (no advIdentity)\n');
|
|
935
958
|
}
|
|
936
959
|
|
|
937
960
|
children.push(new BinaryNode('keys', {}, keysChildren));
|
|
938
961
|
}
|
|
939
962
|
} catch (e) {
|
|
940
|
-
|
|
963
|
+
_whaDbg('[DBG] RETRY prekey bundle error: ' + e.message);
|
|
941
964
|
}
|
|
942
965
|
}
|
|
943
966
|
|
|
@@ -952,7 +975,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
952
975
|
if (origAttrs.recipient) receiptAttrs.recipient = origAttrs.recipient;
|
|
953
976
|
if (origAttrs.participant) receiptAttrs.participant = origAttrs.participant;
|
|
954
977
|
|
|
955
|
-
|
|
978
|
+
_whaDbg('[DBG] RETRY #' + count + ' receipt to=' + JSON.stringify(fromJid) + ' participant=' + JSON.stringify(origAttrs.participant || null));
|
|
956
979
|
this._socket.sendNode(new BinaryNode('receipt', receiptAttrs, children));
|
|
957
980
|
}
|
|
958
981
|
|
|
@@ -1114,7 +1137,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1114
1137
|
_requestMediaConnection() {
|
|
1115
1138
|
if (!this._socket || !this._connected) return;
|
|
1116
1139
|
const mcId = this._genMsgId();
|
|
1117
|
-
|
|
1140
|
+
_whaDbg('[DBG] SEND media_conn IQ id=' + mcId);
|
|
1118
1141
|
this._socket.sendNode(new BinaryNode('iq', {
|
|
1119
1142
|
id: mcId,
|
|
1120
1143
|
to: 's.whatsapp.net',
|
|
@@ -1146,7 +1169,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1146
1169
|
_uploadPreKeys() {
|
|
1147
1170
|
if (!this._signal || !this._socket || !this._connected) return;
|
|
1148
1171
|
|
|
1149
|
-
const preKeys = this._signal.getPreKeysForUpload(
|
|
1172
|
+
const preKeys = this._signal.getPreKeysForUpload(800);
|
|
1150
1173
|
const spk = this._signal.getSignedPreKeyForUpload();
|
|
1151
1174
|
const identKey = this._signal.getIdentityKey();
|
|
1152
1175
|
if (!preKeys.length || !spk) return;
|
|
@@ -1163,7 +1186,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1163
1186
|
]);
|
|
1164
1187
|
|
|
1165
1188
|
const pkId = this._genMsgId();
|
|
1166
|
-
|
|
1189
|
+
_whaDbg('[DBG] SEND uploadPreKeys IQ id=' + pkId);
|
|
1167
1190
|
this._socket.sendNode(new BinaryNode('iq', {
|
|
1168
1191
|
id: pkId,
|
|
1169
1192
|
to: 's.whatsapp.net',
|
|
@@ -1413,7 +1436,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1413
1436
|
})
|
|
1414
1437
|
])
|
|
1415
1438
|
]);
|
|
1416
|
-
|
|
1439
|
+
_whaDbg('[DBG] ISSUE_PRIVACY_TOKENS jid=' + normalizedJid + ' t=' + timestamp + ' iq_id=' + id);
|
|
1417
1440
|
return this._sendIq(node);
|
|
1418
1441
|
}
|
|
1419
1442
|
|
|
@@ -1437,23 +1460,23 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1437
1460
|
|
|
1438
1461
|
// ── Debug: dump result structure so we can trace server response ─────────
|
|
1439
1462
|
if (!result) {
|
|
1440
|
-
|
|
1463
|
+
_whaDbg('[DBG] STORE_TCTOKEN result=NULL (IQ timeout or not matched) fallback=' + fallbackJid);
|
|
1441
1464
|
} else {
|
|
1442
1465
|
const contentLen = Array.isArray(result.content) ? result.content.length
|
|
1443
1466
|
: Buffer.isBuffer(result.content) ? result.content.length
|
|
1444
1467
|
: 0;
|
|
1445
|
-
|
|
1468
|
+
_whaDbg('[DBG] STORE_TCTOKEN result type=' + (result.attrs && result.attrs.type) +
|
|
1446
1469
|
' id=' + (result.attrs && result.attrs.id) +
|
|
1447
|
-
' contentLen=' + contentLen
|
|
1470
|
+
' contentLen=' + contentLen);
|
|
1448
1471
|
if (Array.isArray(result.content)) {
|
|
1449
1472
|
result.content.forEach((c, i) => {
|
|
1450
1473
|
if (c && c.description) {
|
|
1451
1474
|
const childContent = Array.isArray(c.content) ? c.content.length + ' children'
|
|
1452
1475
|
: Buffer.isBuffer(c.content) ? c.content.length + 'B'
|
|
1453
1476
|
: String(c.content);
|
|
1454
|
-
|
|
1477
|
+
_whaDbg('[DBG] STORE_TCTOKEN child[' + i + '] tag=' + c.description +
|
|
1455
1478
|
' attrs=' + JSON.stringify(c.attrs || {}) +
|
|
1456
|
-
' content=' + childContent
|
|
1479
|
+
' content=' + childContent);
|
|
1457
1480
|
}
|
|
1458
1481
|
});
|
|
1459
1482
|
}
|
|
@@ -1486,13 +1509,13 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1486
1509
|
if (bytes && bytes.length) {
|
|
1487
1510
|
this._tcTokenStore.setToken(jid, bytes, t);
|
|
1488
1511
|
tokenStoredCount++;
|
|
1489
|
-
|
|
1512
|
+
_whaDbg('[DBG] TCTOKEN_STORED jid=' + jid + ' t=' + t + ' len=' + bytes.length);
|
|
1490
1513
|
} else {
|
|
1491
|
-
|
|
1514
|
+
_whaDbg('[DBG] STORE_TCTOKEN token node has no bytes jid=' + jid);
|
|
1492
1515
|
}
|
|
1493
1516
|
}
|
|
1494
1517
|
} catch (e) {
|
|
1495
|
-
|
|
1518
|
+
_whaDbg('[DBG] STORE_TCTOKEN parse error: ' + e.message);
|
|
1496
1519
|
}
|
|
1497
1520
|
}
|
|
1498
1521
|
|
|
@@ -1505,8 +1528,8 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1505
1528
|
if (saveSenderTs && issueTs != null) {
|
|
1506
1529
|
try {
|
|
1507
1530
|
this._tcTokenStore.setSenderTimestamp(fallbackJid, issueTs);
|
|
1508
|
-
|
|
1509
|
-
' tokenBytes=' + tokenStoredCount
|
|
1531
|
+
_whaDbg('[DBG] SENDER_TS_SAVED jid=' + fallbackJid + ' ts=' + issueTs +
|
|
1532
|
+
' tokenBytes=' + tokenStoredCount);
|
|
1510
1533
|
} catch (_) {}
|
|
1511
1534
|
}
|
|
1512
1535
|
}
|
|
@@ -1533,14 +1556,14 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1533
1556
|
const { normalizeJidForTcToken } = require('./messages/TcTokenStore');
|
|
1534
1557
|
|
|
1535
1558
|
const attrs = node.attrs || {};
|
|
1536
|
-
|
|
1537
|
-
' sender_lid=' + String(attrs.sender_lid || '')
|
|
1559
|
+
_whaDbg('[DBG] PRIVACY_TOKEN_NOTIF_ENTER from=' + String(attrs.from || '') +
|
|
1560
|
+
' sender_lid=' + String(attrs.sender_lid || ''));
|
|
1538
1561
|
|
|
1539
1562
|
// ── 1. Require <tokens> wrapper (matches Baileys' getBinaryNodeChild check) ──
|
|
1540
1563
|
const content = Array.isArray(node.content) ? node.content : [];
|
|
1541
1564
|
const tokensNode = content.find(n => n && n.description === 'tokens');
|
|
1542
1565
|
if (!tokensNode) {
|
|
1543
|
-
|
|
1566
|
+
_whaDbg('[DBG] PRIVACY_TOKEN_NOTIF no <tokens> wrapper — abort\n');
|
|
1544
1567
|
return;
|
|
1545
1568
|
}
|
|
1546
1569
|
|
|
@@ -1569,7 +1592,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1569
1592
|
storageJid = normalizeJidForTcToken(rawFrom);
|
|
1570
1593
|
}
|
|
1571
1594
|
|
|
1572
|
-
|
|
1595
|
+
_whaDbg('[DBG] PRIVACY_TOKEN_NOTIF storageJid=' + storageJid);
|
|
1573
1596
|
|
|
1574
1597
|
// ── 3. Parse <token> children and store bytes (no senderTimestamp update) ──
|
|
1575
1598
|
// Reuse _storeTcTokenFromIqResult with the full notification node so that its
|
|
@@ -1606,15 +1629,15 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1606
1629
|
return;
|
|
1607
1630
|
}
|
|
1608
1631
|
|
|
1609
|
-
|
|
1610
|
-
' prevSenderTs=' + senderTs
|
|
1632
|
+
_whaDbg('[DBG] REISSUE_TC_TOKEN_AFTER_IDENTITY_CHANGE jid=' + tcJid +
|
|
1633
|
+
' prevSenderTs=' + senderTs);
|
|
1611
1634
|
|
|
1612
1635
|
const issueTs = Math.floor(Date.now() / 1000);
|
|
1613
1636
|
const result = await this._issuePrivacyTokens(tcJid, issueTs);
|
|
1614
1637
|
this._storeTcTokenFromIqResult(result, tcJid, issueTs);
|
|
1615
1638
|
} catch (e) {
|
|
1616
|
-
|
|
1617
|
-
' err=' + (e && e.message)
|
|
1639
|
+
_whaDbg('[DBG] REISSUE_TC_TOKEN_ERR from=' + from +
|
|
1640
|
+
' err=' + (e && e.message));
|
|
1618
1641
|
}
|
|
1619
1642
|
})();
|
|
1620
1643
|
}
|
package/lib/DeviceManager.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { dbg: _whaDbg } = require('./logger');
|
|
4
|
+
|
|
3
5
|
const { BinaryNode } = require('./BinaryNode');
|
|
4
6
|
const { NodeCache } = require('@cacheable/node-cache');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
5
9
|
|
|
6
10
|
const DEVICE_CACHE_TTL = '5m';
|
|
7
11
|
|
|
@@ -141,6 +145,13 @@ class DeviceManager {
|
|
|
141
145
|
// own device list cache + expiry timestamp (refreshed every 5 min)
|
|
142
146
|
this._ownDeviceJids = null;
|
|
143
147
|
this._ownDeviceExpiry = 0;
|
|
148
|
+
// ─── Disk persistence ─────────────────────────────────────────────
|
|
149
|
+
this._cacheSnapshot = {}; // mirrors _deviceCache for serialisation
|
|
150
|
+
this._sessionDir = null;
|
|
151
|
+
this._phone = null;
|
|
152
|
+
this._cacheFile = null;
|
|
153
|
+
this._lidMapFile = null;
|
|
154
|
+
this._diskSaveTimer = null;
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
// ─── NodeCache helper methods ─────────────────────────────────────────────
|
|
@@ -149,24 +160,137 @@ class DeviceManager {
|
|
|
149
160
|
|
|
150
161
|
_dcHas(key) { return this._deviceCache.has(key); }
|
|
151
162
|
_dcGet(key) { return this._deviceCache.get(key) || null; } // number[] | null
|
|
152
|
-
_dcSet(key, arr) { this._deviceCache.set(key, arr); }
|
|
163
|
+
_dcSet(key, arr) { this._deviceCache.set(key, arr); this._cacheSnapshot[key] = arr; }
|
|
153
164
|
_dcAdd(key, id) {
|
|
154
165
|
const arr = this._deviceCache.get(key) || [];
|
|
155
166
|
if (!arr.includes(id)) arr.push(id);
|
|
156
167
|
this._deviceCache.set(key, arr);
|
|
168
|
+
this._cacheSnapshot[key] = arr;
|
|
157
169
|
}
|
|
158
170
|
_dcEnsure(key) {
|
|
159
|
-
if (!this._deviceCache.has(key))
|
|
171
|
+
if (!this._deviceCache.has(key)) {
|
|
172
|
+
this._deviceCache.set(key, []);
|
|
173
|
+
this._cacheSnapshot[key] = [];
|
|
174
|
+
}
|
|
160
175
|
}
|
|
161
176
|
_dcDel(keys) {
|
|
162
|
-
for (const k of keys) this._deviceCache.del(k);
|
|
177
|
+
for (const k of keys) { this._deviceCache.del(k); delete this._cacheSnapshot[k]; }
|
|
163
178
|
}
|
|
164
179
|
_dcFlush() {
|
|
165
180
|
this._deviceCache.flushAll();
|
|
181
|
+
this._cacheSnapshot = {};
|
|
166
182
|
this._ownDeviceJids = null;
|
|
167
183
|
this._ownDeviceExpiry = 0;
|
|
168
184
|
}
|
|
169
185
|
|
|
186
|
+
|
|
187
|
+
// ─── Session-dir attachment & disk persistence ─────────────────────────────
|
|
188
|
+
//
|
|
189
|
+
// Call attachSession(sessionDir, phone) right after DeviceManager construction.
|
|
190
|
+
// It loads the on-disk device cache so the very first send after a process
|
|
191
|
+
// restart is instant — no cold-start usync round-trip needed.
|
|
192
|
+
//
|
|
193
|
+
// Files saved in session dir (baileys-compatible naming):
|
|
194
|
+
// <phone>.device-cache.json ← combined cache (internal)
|
|
195
|
+
// device-list-447911234567@s.whatsapp.net.json ← per-phone device IDs
|
|
196
|
+
// device-list-139471160877194@lid.json ← per-LID device IDs
|
|
197
|
+
// <phone>.lid-mapping.json ← phone ↔ LID mappings
|
|
198
|
+
// <phone>.lid-reverse-mapping.json ← LID → phone mappings
|
|
199
|
+
//
|
|
200
|
+
// Cache validity: only restored if saved < 5 min ago (matches NodeCache TTL).
|
|
201
|
+
// After 5 min the TTL fires in-memory AND the next usync rewrites the files.
|
|
202
|
+
|
|
203
|
+
attachSession(sessionDir, phone) {
|
|
204
|
+
this._sessionDir = sessionDir;
|
|
205
|
+
this._phone = String(phone);
|
|
206
|
+
this._cacheFile = path.join(sessionDir, this._phone + '.device-cache.json');
|
|
207
|
+
this._lidMapFile = path.join(sessionDir, this._phone + '.lid-mapping.json');
|
|
208
|
+
this._loadFromDisk();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
_loadFromDisk() {
|
|
212
|
+
if (!this._cacheFile) return;
|
|
213
|
+
try {
|
|
214
|
+
if (fs.existsSync(this._cacheFile)) {
|
|
215
|
+
const raw = fs.readFileSync(this._cacheFile, 'utf8');
|
|
216
|
+
const data = JSON.parse(raw);
|
|
217
|
+
const age = Date.now() - (data.saved || 0);
|
|
218
|
+
if (data.entries && age < 5 * 60 * 1000) {
|
|
219
|
+
for (const [key, ids] of Object.entries(data.entries)) {
|
|
220
|
+
if (Array.isArray(ids)) {
|
|
221
|
+
this._deviceCache.set(key, ids);
|
|
222
|
+
this._cacheSnapshot[key] = ids;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
_whaDbg('[DBG] DEVICE_CACHE_LOADED entries=' + Object.keys(data.entries).length + ' age=' + Math.round(age / 1000) + 's');
|
|
226
|
+
} else if (data.entries) {
|
|
227
|
+
_whaDbg('[DBG] DEVICE_CACHE_STALE age=' + Math.round(age / 1000) + 's — usync will re-query');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} catch (e) {
|
|
231
|
+
_whaDbg('[DBG] DEVICE_CACHE_LOAD_ERR ' + e.message);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Debounced save (300 ms) so rapid usync responses don't cause I/O storms
|
|
236
|
+
_scheduleSave() {
|
|
237
|
+
if (!this._cacheFile) return;
|
|
238
|
+
if (this._diskSaveTimer) clearTimeout(this._diskSaveTimer);
|
|
239
|
+
this._diskSaveTimer = setTimeout(() => {
|
|
240
|
+
this._diskSaveTimer = null;
|
|
241
|
+
this._saveToDisk();
|
|
242
|
+
}, 300);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
_saveToDisk() {
|
|
246
|
+
if (!this._sessionDir || !this._cacheFile) return;
|
|
247
|
+
try {
|
|
248
|
+
const entries = Object.assign({}, this._cacheSnapshot);
|
|
249
|
+
|
|
250
|
+
// ── Combined cache file ─────────────────────────────────────────────────
|
|
251
|
+
fs.writeFileSync(
|
|
252
|
+
this._cacheFile,
|
|
253
|
+
JSON.stringify({ saved: Date.now(), ttl: 300000, entries }, null, 2),
|
|
254
|
+
'utf8'
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// ── Individual device-list files (baileys-compatible) ──────────────────
|
|
258
|
+
for (const [key, ids] of Object.entries(entries)) {
|
|
259
|
+
let fileName;
|
|
260
|
+
if (key.startsWith('lid:')) {
|
|
261
|
+
fileName = 'device-list-' + key.slice(4) + '@lid.json';
|
|
262
|
+
} else {
|
|
263
|
+
fileName = 'device-list-' + key + '@s.whatsapp.net.json';
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
fs.writeFileSync(path.join(this._sessionDir, fileName), JSON.stringify(ids), 'utf8');
|
|
267
|
+
} catch (_) {}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ── LID mapping files ────────────────────────────────────────────────────
|
|
271
|
+
if (this._client._pnToLid && this._client._lidToPn) {
|
|
272
|
+
const pnToLid = {};
|
|
273
|
+
const lidToPn = {};
|
|
274
|
+
for (const [pn, lid] of this._client._pnToLid) { pnToLid[pn] = lid; }
|
|
275
|
+
for (const [lid, pn] of this._client._lidToPn) { lidToPn[lid] = pn; }
|
|
276
|
+
fs.writeFileSync(
|
|
277
|
+
this._lidMapFile,
|
|
278
|
+
JSON.stringify({ pnToLid, lidToPn }, null, 2),
|
|
279
|
+
'utf8'
|
|
280
|
+
);
|
|
281
|
+
fs.writeFileSync(
|
|
282
|
+
path.join(this._sessionDir, this._phone + '.lid-reverse-mapping.json'),
|
|
283
|
+
JSON.stringify(lidToPn, null, 2),
|
|
284
|
+
'utf8'
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
_whaDbg('[DBG] DEVICE_CACHE_SAVED entries=' + Object.keys(entries).length);
|
|
289
|
+
} catch (e) {
|
|
290
|
+
_whaDbg('[DBG] DEVICE_CACHE_SAVE_ERR ' + e.message);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
170
294
|
// ─── Fetch pre-key bundles for a list of JIDs via encrypt IQ ───────────────
|
|
171
295
|
// Only called for JIDs where no Signal session exists yet.
|
|
172
296
|
// Returns Map<string_jid, bundle> — keys are always normalised strings.
|
|
@@ -203,20 +327,20 @@ class DeviceManager {
|
|
|
203
327
|
[new BinaryNode('key', {}, userNodes)]
|
|
204
328
|
);
|
|
205
329
|
|
|
206
|
-
|
|
330
|
+
_whaDbg('[DBG] FETCH_BUNDLES jids=[' + jids.join(',') + ']\n');
|
|
207
331
|
|
|
208
332
|
const response = await this._client._sendIq(iqNode);
|
|
209
333
|
const bundles = new Map();
|
|
210
334
|
if (!response) {
|
|
211
|
-
|
|
335
|
+
_whaDbg('[DBG] FETCH_BUNDLES_NULL\n');
|
|
212
336
|
return bundles;
|
|
213
337
|
}
|
|
214
338
|
|
|
215
339
|
const listNode = findChild(response, 'list');
|
|
216
340
|
const children = listNode ? listNode.content : (response.content || []);
|
|
217
341
|
|
|
218
|
-
|
|
219
|
-
(Array.isArray(children) ? children.length : 0)
|
|
342
|
+
_whaDbg('[DBG] FETCH_BUNDLES_RESP childCount=' +
|
|
343
|
+
(Array.isArray(children) ? children.length : 0));
|
|
220
344
|
|
|
221
345
|
for (const userNode of (Array.isArray(children) ? children : [])) {
|
|
222
346
|
if (!userNode || userNode.description !== 'user') continue;
|
|
@@ -224,15 +348,15 @@ class DeviceManager {
|
|
|
224
348
|
if (!jidRaw) continue;
|
|
225
349
|
// Always normalise to a string key so Map lookups are consistent
|
|
226
350
|
const jidStr = String(jidRaw);
|
|
227
|
-
|
|
351
|
+
_whaDbg('[DBG] FETCH_BUNDLES_USER jid=' + jidStr +
|
|
228
352
|
' childTags=' + (Array.isArray(userNode.content)
|
|
229
353
|
? userNode.content.map(c => c && c.description).filter(Boolean).join(',')
|
|
230
|
-
: '')
|
|
354
|
+
: ''));
|
|
231
355
|
const bundle = parseBundleFromUserNode(userNode);
|
|
232
356
|
if (bundle) bundles.set(jidStr, bundle);
|
|
233
|
-
else
|
|
357
|
+
else _whaDbg('[DBG] FETCH_BUNDLES_NO_BUNDLE jid=' + jidStr);
|
|
234
358
|
}
|
|
235
|
-
|
|
359
|
+
_whaDbg('[DBG] FETCH_BUNDLES_DONE count=' + bundles.size);
|
|
236
360
|
return bundles;
|
|
237
361
|
}
|
|
238
362
|
|
|
@@ -332,10 +456,10 @@ class DeviceManager {
|
|
|
332
456
|
)]
|
|
333
457
|
);
|
|
334
458
|
|
|
335
|
-
|
|
459
|
+
_whaDbg('[DBG] USYNC_IQ phones=[' + phones.join(',') + ']\n');
|
|
336
460
|
|
|
337
461
|
const response = await this._client._sendIq(iqNode).catch(err => {
|
|
338
|
-
|
|
462
|
+
_whaDbg('[DBG] USYNC_ERR ' + (err && err.message));
|
|
339
463
|
return null;
|
|
340
464
|
});
|
|
341
465
|
|
|
@@ -383,7 +507,7 @@ class DeviceManager {
|
|
|
383
507
|
} else {
|
|
384
508
|
cachePhone = rawUser; // last resort: use LID user as key
|
|
385
509
|
}
|
|
386
|
-
|
|
510
|
+
_whaDbg('[DBG] USYNC_LID_USER userJid=' + userJid + ' → cachePhone=' + cachePhone);
|
|
387
511
|
} else {
|
|
388
512
|
// Normal account: user JID is phone@s.whatsapp.net
|
|
389
513
|
cachePhone = rawUser;
|
|
@@ -394,7 +518,7 @@ class DeviceManager {
|
|
|
394
518
|
const lidUser = String(lidAttr).split('@')[0].split(':')[0];
|
|
395
519
|
if (this._client._lidToPn) this._client._lidToPn.set(lidUser, cachePhone);
|
|
396
520
|
if (this._client._pnToLid) this._client._pnToLid.set(cachePhone, lidUser);
|
|
397
|
-
|
|
521
|
+
_whaDbg('[DBG] USYNC_LID_MAP phone=' + cachePhone + ' ↔ lid=' + lidUser);
|
|
398
522
|
}
|
|
399
523
|
}
|
|
400
524
|
|
|
@@ -413,19 +537,19 @@ class DeviceManager {
|
|
|
413
537
|
: 0;
|
|
414
538
|
this._dcAdd(cachePhone, devId);
|
|
415
539
|
}
|
|
416
|
-
|
|
540
|
+
_whaDbg('[DBG] USYNC_DEVICES phone=' + cachePhone +
|
|
417
541
|
' ids=[' + (this._dcGet(cachePhone) || []).join(',') + ']\n');
|
|
418
542
|
} else {
|
|
419
543
|
// No device-list in response — fall back to device 0
|
|
420
544
|
this._dcAdd(cachePhone, 0);
|
|
421
|
-
|
|
545
|
+
_whaDbg('[DBG] USYNC_NO_DEVLIST phone=' + cachePhone + ' → fallback id=0\n');
|
|
422
546
|
}
|
|
423
547
|
}
|
|
424
548
|
} else {
|
|
425
|
-
|
|
549
|
+
_whaDbg('[DBG] USYNC_RESP_NO_LIST\n');
|
|
426
550
|
}
|
|
427
551
|
} else {
|
|
428
|
-
|
|
552
|
+
_whaDbg('[DBG] USYNC_NULL_RESP\n');
|
|
429
553
|
// Device usync timed out — fall back to a contact usync (query/contact).
|
|
430
554
|
// This DOES receive a server response and resolves the phone → LID mapping.
|
|
431
555
|
// Once _pnToLid is populated here, _sendDMMessage (after its await) will
|
|
@@ -437,9 +561,10 @@ class DeviceManager {
|
|
|
437
561
|
for (const p of phones) {
|
|
438
562
|
if (!this._dcHas(p)) {
|
|
439
563
|
this._dcSet(p, [0]);
|
|
440
|
-
|
|
564
|
+
_whaDbg('[DBG] USYNC_FALLBACK phone=' + p);
|
|
441
565
|
}
|
|
442
566
|
}
|
|
567
|
+
this._scheduleSave();
|
|
443
568
|
}
|
|
444
569
|
|
|
445
570
|
// ─── Ensure sessions exist for all devices of a set of recipients ──────────
|
|
@@ -508,10 +633,10 @@ class DeviceManager {
|
|
|
508
633
|
// desktop, web) are silently ignored and only device 0 (primary phone) is used.
|
|
509
634
|
if (skipUsync) {
|
|
510
635
|
if (!this._dcHas(cacheKey)) {
|
|
511
|
-
|
|
636
|
+
_whaDbg('[DBG] LID_SKIP_USYNC_CACHE_MISS lidUser=' + lidUser + ' → real usync\n');
|
|
512
637
|
await this._doUsyncIqByJid(lidJid, cacheKey, lidUser);
|
|
513
638
|
} else {
|
|
514
|
-
|
|
639
|
+
_whaDbg('[DBG] LID_SKIP_USYNC_HIT lidUser=' + lidUser + ' → cache\n');
|
|
515
640
|
}
|
|
516
641
|
} else if (!this._dcHas(cacheKey)) {
|
|
517
642
|
await this._doUsyncIqByJid(lidJid, cacheKey, lidUser);
|
|
@@ -520,7 +645,7 @@ class DeviceManager {
|
|
|
520
645
|
const deviceIds = this._dcGet(cacheKey) || [0];
|
|
521
646
|
const deviceJids = deviceIds.map(d => makeDeviceJid(lidUser, d, 'lid'));
|
|
522
647
|
|
|
523
|
-
|
|
648
|
+
_whaDbg('[DBG] LID_DEVICES lidUser=' + lidUser +
|
|
524
649
|
' deviceJids=[' + deviceJids.join(',') + ']\n');
|
|
525
650
|
|
|
526
651
|
// Split: existing sessions (ready) vs new (need bundle fetch)
|
|
@@ -540,9 +665,9 @@ class DeviceManager {
|
|
|
540
665
|
try {
|
|
541
666
|
await signalProto.buildSessionFromBundle(jid, bundle);
|
|
542
667
|
readyJids.push(jid);
|
|
543
|
-
|
|
668
|
+
_whaDbg('[DBG] LID_SESSION_BUILT jid=' + jid);
|
|
544
669
|
} catch (e) {
|
|
545
|
-
|
|
670
|
+
_whaDbg('[DBG] LID_SESSION_ERR jid=' + jid + ' err=' + e.message);
|
|
546
671
|
}
|
|
547
672
|
}
|
|
548
673
|
}
|
|
@@ -584,15 +709,15 @@ class DeviceManager {
|
|
|
584
709
|
)]
|
|
585
710
|
);
|
|
586
711
|
|
|
587
|
-
|
|
712
|
+
_whaDbg('[DBG] CONTACT_USYNC phones=[' + phones.join(',') + ']\n');
|
|
588
713
|
|
|
589
714
|
const response = await this._client._sendIq(iqNode).catch(err => {
|
|
590
|
-
|
|
715
|
+
_whaDbg('[DBG] CONTACT_USYNC_ERR ' + (err && err.message));
|
|
591
716
|
return null;
|
|
592
717
|
});
|
|
593
718
|
|
|
594
719
|
if (!response) {
|
|
595
|
-
|
|
720
|
+
_whaDbg('[DBG] CONTACT_USYNC_NULL\n');
|
|
596
721
|
return;
|
|
597
722
|
}
|
|
598
723
|
|
|
@@ -600,7 +725,7 @@ class DeviceManager {
|
|
|
600
725
|
const listNode = usyncNode ? findChild(usyncNode, 'list') : findChild(response, 'list');
|
|
601
726
|
|
|
602
727
|
if (!listNode || !Array.isArray(listNode.content)) {
|
|
603
|
-
|
|
728
|
+
_whaDbg('[DBG] CONTACT_USYNC_NO_LIST\n');
|
|
604
729
|
return;
|
|
605
730
|
}
|
|
606
731
|
|
|
@@ -620,7 +745,7 @@ class DeviceManager {
|
|
|
620
745
|
// unambiguous; for multi-phone queries match by index.
|
|
621
746
|
const phone = phones.length === 1 ? phones[0] : phones[i];
|
|
622
747
|
if (phone) {
|
|
623
|
-
|
|
748
|
+
_whaDbg('[DBG] CONTACT_USYNC_LID phone=' + phone + ' → lid=' + lidUser);
|
|
624
749
|
if (this._client._pnToLid) this._client._pnToLid.set(phone, lidUser);
|
|
625
750
|
if (this._client._lidToPn) this._client._lidToPn.set(lidUser, phone);
|
|
626
751
|
// Persist the mapping so future sessions don't need to re-query
|
|
@@ -631,9 +756,10 @@ class DeviceManager {
|
|
|
631
756
|
// PN account — note it (still on phone-based routing, no LID needed)
|
|
632
757
|
const { user: pnUser } = stripUser(actualJidStr);
|
|
633
758
|
const phone = phones.length === 1 ? phones[0] : phones[i];
|
|
634
|
-
|
|
759
|
+
_whaDbg('[DBG] CONTACT_USYNC_PN phone=' + phone + ' jid=' + actualJidStr);
|
|
635
760
|
}
|
|
636
761
|
}
|
|
762
|
+
this._scheduleSave();
|
|
637
763
|
}
|
|
638
764
|
|
|
639
765
|
// ─── usync IQ for a specific JID (LID or phone-based) ─────────────────────
|
|
@@ -658,10 +784,10 @@ class DeviceManager {
|
|
|
658
784
|
)]
|
|
659
785
|
);
|
|
660
786
|
|
|
661
|
-
|
|
787
|
+
_whaDbg('[DBG] USYNC_LID_IQ jid=' + jid);
|
|
662
788
|
|
|
663
789
|
const response = await this._client._sendIq(iqNode).catch(err => {
|
|
664
|
-
|
|
790
|
+
_whaDbg('[DBG] USYNC_LID_IQ_ERR ' + (err && err.message));
|
|
665
791
|
return null;
|
|
666
792
|
});
|
|
667
793
|
|
|
@@ -685,21 +811,22 @@ class DeviceManager {
|
|
|
685
811
|
? parseInt(String(devNode.attrs.id), 10) : 0;
|
|
686
812
|
this._dcAdd(cacheKey, devId);
|
|
687
813
|
}
|
|
688
|
-
|
|
814
|
+
_whaDbg('[DBG] USYNC_LID_DEVICES jid=' + jid +
|
|
689
815
|
' ids=[' + (this._dcGet(cacheKey) || []).join(',') + ']\n');
|
|
690
816
|
} else {
|
|
691
817
|
this._dcAdd(cacheKey, 0);
|
|
692
|
-
|
|
818
|
+
_whaDbg('[DBG] USYNC_LID_NO_DEVLIST jid=' + jid + ' → fallback id=0\n');
|
|
693
819
|
}
|
|
694
820
|
}
|
|
695
821
|
} else {
|
|
696
|
-
|
|
822
|
+
_whaDbg('[DBG] USYNC_LID_NO_LIST jid=' + jid);
|
|
697
823
|
this._dcAdd(cacheKey, 0);
|
|
698
824
|
}
|
|
699
825
|
} else {
|
|
700
|
-
|
|
826
|
+
_whaDbg('[DBG] USYNC_LID_NULL_RESP jid=' + jid);
|
|
701
827
|
this._dcAdd(cacheKey, 0);
|
|
702
828
|
}
|
|
829
|
+
this._scheduleSave();
|
|
703
830
|
}
|
|
704
831
|
|
|
705
832
|
// ─── Ensure sessions for own linked devices (device != 0) ─────────────────
|
package/lib/Registration.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { dbg: _whaDbg } = require('./logger');
|
|
4
|
+
|
|
3
5
|
const crypto = require('crypto');
|
|
4
6
|
const https = require('https');
|
|
5
7
|
const tls = require('tls');
|
|
@@ -691,7 +693,7 @@ async function requestSmsCode(store, method, opts) {
|
|
|
691
693
|
// Auto-fallback on no_routes
|
|
692
694
|
if (result && result._noRoutes && !autoFallbackDone) {
|
|
693
695
|
autoFallbackDone = true;
|
|
694
|
-
|
|
696
|
+
_whaDbg(`[REG] ${method} returned no_routes — auto-trying ${fallbackMethod}`);
|
|
695
697
|
result = await _tryMethod(fallbackMethod);
|
|
696
698
|
}
|
|
697
699
|
|
package/lib/Store.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { warn: _whaWarn } = require('./logger');
|
|
4
|
+
|
|
3
5
|
const fs = require('fs');
|
|
4
6
|
const path = require('path');
|
|
5
7
|
const crypto = require('crypto');
|
|
@@ -182,7 +184,7 @@ function storeFromJson(obj) {
|
|
|
182
184
|
function saveStore(store, filePath) {
|
|
183
185
|
// MAIN FIX: avoid crashing when called with undefined/null store
|
|
184
186
|
if (!store) {
|
|
185
|
-
|
|
187
|
+
_whaWarn('saveStore: called with empty store, skipping write for ' + filePath);
|
|
186
188
|
return;
|
|
187
189
|
}
|
|
188
190
|
|
package/lib/logger.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _noop = {
|
|
4
|
+
trace: () => {},
|
|
5
|
+
debug: () => {},
|
|
6
|
+
info: () => {},
|
|
7
|
+
warn: () => {},
|
|
8
|
+
error: () => {}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let _logger = _noop;
|
|
12
|
+
|
|
13
|
+
function configureLogger(opts) {
|
|
14
|
+
if (!opts || opts === false) {
|
|
15
|
+
_logger = _noop;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const pino = require('pino');
|
|
20
|
+
const pinoOpts = (opts === true) ? { level: 'debug' } : opts;
|
|
21
|
+
_logger = pino(pinoOpts);
|
|
22
|
+
} catch (_) {
|
|
23
|
+
_logger = _noop;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getLogger() {
|
|
28
|
+
return _logger;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function dbg(msg) { _logger.debug(msg); }
|
|
32
|
+
function warn(msg) { _logger.warn(msg); }
|
|
33
|
+
function err(msg) { _logger.error(msg); }
|
|
34
|
+
function info(msg) { _logger.info(msg); }
|
|
35
|
+
|
|
36
|
+
module.exports = { configureLogger, getLogger, dbg, warn, err, info };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { dbg: _whaDbg } = require('../logger');
|
|
4
|
+
|
|
3
5
|
const crypto = require('crypto');
|
|
4
6
|
const { Mutex } = require('async-mutex');
|
|
5
7
|
const path = require('path');
|
|
@@ -129,10 +131,10 @@ function _buildOrGetAdvIdentity(store) {
|
|
|
129
131
|
|
|
130
132
|
const adv = _encodeADVSignedIdentity(details, pub32, acctSig, devSig);
|
|
131
133
|
store.advIdentity = adv;
|
|
132
|
-
|
|
134
|
+
_whaDbg('[DBG] ADV_BUILT from primary iOS keys (' + adv.length + 'b)\n');
|
|
133
135
|
return adv;
|
|
134
136
|
} catch (e) {
|
|
135
|
-
|
|
137
|
+
_whaDbg('[DBG] ADV_BUILD_ERR: ' + e.message);
|
|
136
138
|
return null;
|
|
137
139
|
}
|
|
138
140
|
}
|
|
@@ -581,8 +583,8 @@ class MessageSender {
|
|
|
581
583
|
const lidUser = this._client._pnToLid && this._client._pnToLid.get(recipientPhone);
|
|
582
584
|
const routingToJid = lidUser ? `${lidUser}@lid` : toJid;
|
|
583
585
|
|
|
584
|
-
|
|
585
|
-
' routing=' + routingToJid + (lidUser ? ' (LID)' : ' (PN)')
|
|
586
|
+
_whaDbg('[DBG] DM_ROUTE phone=' + recipientPhone +
|
|
587
|
+
' routing=' + routingToJid + (lidUser ? ' (LID)' : ' (PN)'));
|
|
586
588
|
|
|
587
589
|
let otherJids;
|
|
588
590
|
if (lidUser) {
|
|
@@ -652,11 +654,11 @@ class MessageSender {
|
|
|
652
654
|
if (tcEntry && tcEntry.token && tcEntry.token.length) {
|
|
653
655
|
if (!tcTokenExpired(tcEntry.timestamp)) {
|
|
654
656
|
msgContent.push(new BinaryNode('tctoken', {}, tcEntry.token));
|
|
655
|
-
|
|
657
|
+
_whaDbg('[DBG] TCTOKEN_ATTACH jid=' + tcJid);
|
|
656
658
|
} else {
|
|
657
659
|
// Expired token — clear it, keep senderTimestamp for dedupe
|
|
658
660
|
tcStore.clearToken(tcJid);
|
|
659
|
-
|
|
661
|
+
_whaDbg('[DBG] TCTOKEN_EXPIRED jid=' + tcJid + ' — cleared\n');
|
|
660
662
|
}
|
|
661
663
|
}
|
|
662
664
|
}
|
|
@@ -665,16 +667,15 @@ class MessageSender {
|
|
|
665
667
|
const msgNode = new BinaryNode('message', stanzaAttrs, msgContent);
|
|
666
668
|
|
|
667
669
|
// Debug: log outgoing stanza details
|
|
668
|
-
|
|
670
|
+
_whaDbg('[DBG] DM_SEND to=' + toJid +
|
|
669
671
|
' otherJids=[' + otherJids.join(',') + ']' +
|
|
670
672
|
' ownLinked=[' + ownLinkedJids.join(',') + ']' +
|
|
671
673
|
' encrypted=' + encryptedList.length +
|
|
672
674
|
' hasPkmsg=' + hasPkmsg +
|
|
673
|
-
' hasAdv=' + !!(advBytes)
|
|
674
|
-
'\n');
|
|
675
|
+
' hasAdv=' + !!(advBytes));
|
|
675
676
|
if (encryptedList.length > 0) {
|
|
676
|
-
|
|
677
|
-
encryptedList.map(e => e.jid + '(' + e.type + ')').join(', ')
|
|
677
|
+
_whaDbg('[DBG] DM_PARTICIPANTS ' +
|
|
678
|
+
encryptedList.map(e => e.jid + '(' + e.type + ')').join(', '));
|
|
678
679
|
}
|
|
679
680
|
|
|
680
681
|
// Cache plaintext so Client can re-send with fresh session on recipient retry
|
|
@@ -722,7 +723,7 @@ class MessageSender {
|
|
|
722
723
|
let members = this._client._getGroupMembers(groupJid);
|
|
723
724
|
if (members.length === 0 && typeof this._client.getGroupMetadata === 'function') {
|
|
724
725
|
try {
|
|
725
|
-
|
|
726
|
+
_whaDbg('[DBG] GROUP_SEND auto-fetch metadata for ' + groupJid);
|
|
726
727
|
await this._client.getGroupMetadata(groupJid);
|
|
727
728
|
members = this._client._getGroupMembers(groupJid);
|
|
728
729
|
} catch (_) {}
|
|
@@ -745,7 +746,7 @@ class MessageSender {
|
|
|
745
746
|
if (isLid) {
|
|
746
747
|
const pn = this._client._lidToPn && this._client._lidToPn.get(raw);
|
|
747
748
|
if (!pn) {
|
|
748
|
-
|
|
749
|
+
_whaDbg('[DBG] GROUP_SEND skip LID member with no PN mapping: ' + jid);
|
|
749
750
|
return null;
|
|
750
751
|
}
|
|
751
752
|
return pn;
|
|
@@ -859,16 +860,14 @@ class MessageSender {
|
|
|
859
860
|
const tcJid = normalizeJidForTcToken(
|
|
860
861
|
ackFrom || String(node.attrs && node.attrs.to || '')
|
|
861
862
|
);
|
|
862
|
-
|
|
863
|
-
'
|
|
864
|
-
' tcJid=' + tcJid + ' — issuing tctoken then retrying\n'
|
|
865
|
-
);
|
|
863
|
+
_whaDbg('[DBG] ACK_463 from=' + ackFrom + ' id=' + id +
|
|
864
|
+
' tcJid=' + tcJid + ' — issuing tctoken then retrying\n');
|
|
866
865
|
|
|
867
866
|
if (client._inFlight463Recoveries.has(tcJid)) {
|
|
868
867
|
// Another concurrent recovery in progress for this JID —
|
|
869
868
|
// wait ~3 s for it to finish then retry with whatever token
|
|
870
869
|
// is now stored (may or may not succeed).
|
|
871
|
-
|
|
870
|
+
_whaDbg('[DBG] ACK_463 already in-flight for ' + tcJid + ' — retrying after delay\n');
|
|
872
871
|
setTimeout(() => {
|
|
873
872
|
const retryId = crypto.randomBytes(8).toString('hex').toUpperCase();
|
|
874
873
|
const retryNode = new BinaryNode(
|
|
@@ -897,16 +896,14 @@ class MessageSender {
|
|
|
897
896
|
const entry = tcStore && tcStore.get(tcJid);
|
|
898
897
|
const hasToken = entry && entry.token && entry.token.length > 0;
|
|
899
898
|
if (!hasToken) {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
tcJid + ', giving up (account may be restricted)\n'
|
|
903
|
-
);
|
|
899
|
+
_whaDbg('[DBG] ACK_463 IQ timeout or server refused — no token bytes stored for ' +
|
|
900
|
+
tcJid + ', giving up (account may be restricted)\n');
|
|
904
901
|
throw new Error(
|
|
905
902
|
'Send error 463: server did not issue a tctoken for ' + tcJid +
|
|
906
903
|
' (IQ timeout or account restricted)'
|
|
907
904
|
);
|
|
908
905
|
}
|
|
909
|
-
|
|
906
|
+
_whaDbg('[DBG] ACK_463 token stored for ' + tcJid + ' — retrying send\n');
|
|
910
907
|
const retryId = crypto.randomBytes(8).toString('hex').toUpperCase();
|
|
911
908
|
const retryNode = new BinaryNode(
|
|
912
909
|
node.description,
|
|
@@ -917,7 +914,7 @@ class MessageSender {
|
|
|
917
914
|
})
|
|
918
915
|
.then(r => resolve(r))
|
|
919
916
|
.catch(err => {
|
|
920
|
-
|
|
917
|
+
_whaDbg('[DBG] ACK_463 recovery failed: ' + (err && err.message));
|
|
921
918
|
reject(new Error('Send error 463: tctoken recovery failed — ' + (err && err.message)));
|
|
922
919
|
})
|
|
923
920
|
.finally(() => client._inFlight463Recoveries.delete(tcJid));
|
package/lib/noise.js
CHANGED
|
@@ -202,6 +202,8 @@ class NoiseSocket extends EventEmitter {
|
|
|
202
202
|
this._awaitingAuth = false; // true after ClientFinish sent, before server <success>/<failure>
|
|
203
203
|
this.rxBuf = Buffer.alloc(0);
|
|
204
204
|
this._ephemeralKeyPair = null;
|
|
205
|
+
this._lastRxAt = Date.now(); // updated on every TCP data received
|
|
206
|
+
this._connectTimeoutTimer = null; // cleared once TCP connect fires
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
connect() {
|
|
@@ -210,7 +212,23 @@ class NoiseSocket extends EventEmitter {
|
|
|
210
212
|
this._connectReject = reject;
|
|
211
213
|
|
|
212
214
|
this.socket = net.createConnection({ host: WHATSAPP_HOST, port: WHATSAPP_PORT });
|
|
213
|
-
|
|
215
|
+
// OS-level TCP keepalive: detects silently-dead NAT connections that never send FIN/RST
|
|
216
|
+
this.socket.setKeepAlive(true, 15000);
|
|
217
|
+
// Connect-phase watchdog: abort if TCP handshake takes >20s
|
|
218
|
+
this._connectTimeoutTimer = setTimeout(() => {
|
|
219
|
+
if (this._connectReject) {
|
|
220
|
+
const err = new Error('WA TCP connect timeout (20s)');
|
|
221
|
+
this._connectReject(err);
|
|
222
|
+
this._connectResolve = null;
|
|
223
|
+
this._connectReject = null;
|
|
224
|
+
}
|
|
225
|
+
try { this.socket.destroy(); } catch (_) {}
|
|
226
|
+
}, 20000);
|
|
227
|
+
this.socket.on('connect', () => {
|
|
228
|
+
clearTimeout(this._connectTimeoutTimer);
|
|
229
|
+
this._connectTimeoutTimer = null;
|
|
230
|
+
this._onTcpConnect();
|
|
231
|
+
});
|
|
214
232
|
this.socket.on('data', (data) => this._onData(data));
|
|
215
233
|
this.socket.on('error', (err) => this._onTcpError(err));
|
|
216
234
|
this.socket.on('close', () => this._onTcpClose());
|
|
@@ -238,6 +256,7 @@ class NoiseSocket extends EventEmitter {
|
|
|
238
256
|
}
|
|
239
257
|
|
|
240
258
|
_onData(data) {
|
|
259
|
+
this._lastRxAt = Date.now(); // watchdog uses this to detect dead connections
|
|
241
260
|
this.rxBuf = Buffer.concat([this.rxBuf, data]);
|
|
242
261
|
this._processBuffer();
|
|
243
262
|
}
|
|
@@ -294,7 +313,7 @@ class NoiseSocket extends EventEmitter {
|
|
|
294
313
|
shortConnect: false,
|
|
295
314
|
connectType: 1,
|
|
296
315
|
connectReason: 1,
|
|
297
|
-
connectAttemptCount: 0,
|
|
316
|
+
connectAttemptCount: (this.store.connectAttemptCount || 0),
|
|
298
317
|
device: 0,
|
|
299
318
|
oc: false,
|
|
300
319
|
userAgent: {
|
|
@@ -407,6 +426,8 @@ class NoiseSocket extends EventEmitter {
|
|
|
407
426
|
}
|
|
408
427
|
|
|
409
428
|
_onTcpError(err) {
|
|
429
|
+
clearTimeout(this._connectTimeoutTimer);
|
|
430
|
+
this._connectTimeoutTimer = null;
|
|
410
431
|
if (this._connectReject) {
|
|
411
432
|
this._connectReject(err);
|
|
412
433
|
this._connectResolve = null;
|
|
@@ -416,8 +437,17 @@ class NoiseSocket extends EventEmitter {
|
|
|
416
437
|
}
|
|
417
438
|
|
|
418
439
|
_onTcpClose() {
|
|
440
|
+
clearTimeout(this._connectTimeoutTimer);
|
|
441
|
+
this._connectTimeoutTimer = null;
|
|
419
442
|
this.connected = false;
|
|
420
443
|
this.secured = false;
|
|
444
|
+
// If the TCP connection closed while the WA handshake was still in progress,
|
|
445
|
+
// the Promise returned by connect() would hang forever — reject it now.
|
|
446
|
+
if (this._connectReject) {
|
|
447
|
+
this._connectReject(new Error('WA TCP closed during handshake'));
|
|
448
|
+
this._connectResolve = null;
|
|
449
|
+
this._connectReject = null;
|
|
450
|
+
}
|
|
421
451
|
this.emit('close');
|
|
422
452
|
}
|
|
423
453
|
|
|
@@ -7,8 +7,8 @@ const { SenderKeyStore, SenderKeyCrypto } = require('./SenderKey');
|
|
|
7
7
|
|
|
8
8
|
const { SessionCipher, SessionBuilder, ProtocolAddress, keyhelper } = libsignal;
|
|
9
9
|
|
|
10
|
-
const PRE_KEY_COUNT =
|
|
11
|
-
const PRE_KEY_MIN =
|
|
10
|
+
const PRE_KEY_COUNT = 800;
|
|
11
|
+
const PRE_KEY_MIN = 50;
|
|
12
12
|
const PRE_KEY_START = 1;
|
|
13
13
|
|
|
14
14
|
// ─── Per-JID async mutex ────────────
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { dbg: _whaDbg, warn: _whaWarn, err: _whaErr, info: _whaInfo } = require('../../logger');
|
|
2
|
+
|
|
1
3
|
|
|
2
4
|
'use strict';
|
|
3
5
|
|
|
@@ -40,7 +42,7 @@ function scrubPubKeyFormat(pubKey) {
|
|
|
40
42
|
if (pubKey.byteLength == 33) {
|
|
41
43
|
return pubKey.slice(1);
|
|
42
44
|
} else {
|
|
43
|
-
|
|
45
|
+
_whaErr('WARNING: Expected pubkey of length 33, please report the ST and client that generated the pubkey');
|
|
44
46
|
return pubKey;
|
|
45
47
|
}
|
|
46
48
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { dbg: _whaDbg, warn: _whaWarn, err: _whaErr, info: _whaInfo } = require('../../logger');
|
|
2
|
+
|
|
1
3
|
// vim: ts=4:sw=4:expandtab
|
|
2
4
|
|
|
3
5
|
/*
|
|
@@ -47,7 +49,7 @@ module.exports = function(bucket, awaitable) {
|
|
|
47
49
|
if (typeof bucket === 'string') {
|
|
48
50
|
awaitable.name = bucket;
|
|
49
51
|
} else {
|
|
50
|
-
|
|
52
|
+
_whaWarn('Unhandled bucket type (for naming): ' + typeof bucket);
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
let inactive;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { dbg: _whaDbg, warn: _whaWarn, err: _whaErr, info: _whaInfo } = require('../../logger');
|
|
2
|
+
|
|
1
3
|
|
|
2
4
|
'use strict';
|
|
3
5
|
|
|
@@ -71,7 +73,7 @@ class SessionBuilder {
|
|
|
71
73
|
}
|
|
72
74
|
const existingOpenSession = record.getOpenSession();
|
|
73
75
|
if (existingOpenSession) {
|
|
74
|
-
|
|
76
|
+
_whaWarn('Closing open session in favor of incoming prekey bundle');
|
|
75
77
|
record.closeSession(existingOpenSession);
|
|
76
78
|
}
|
|
77
79
|
record.setSession(await this.initSession(false, preKeyPair, signedPreKeyPair,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { dbg: _whaDbg, warn: _whaWarn, err: _whaErr, info: _whaInfo } = require('../../logger');
|
|
2
|
+
|
|
1
3
|
// vim: ts=4:sw=4:expandtab
|
|
2
4
|
|
|
3
5
|
const ChainType = require('./chain_type');
|
|
@@ -175,7 +177,7 @@ class SessionCipher {
|
|
|
175
177
|
// was the most current. Simply make a note of it and continue. If our
|
|
176
178
|
// actual open session is for reason invalid, that must be handled via
|
|
177
179
|
// a full SessionError response.
|
|
178
|
-
|
|
180
|
+
_whaWarn('Decrypted message with closed session.');
|
|
179
181
|
}
|
|
180
182
|
await this.storeRecord(record);
|
|
181
183
|
return result.plaintext;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { dbg: _whaDbg, warn: _whaWarn, err: _whaErr, info: _whaInfo } = require('../../logger');
|
|
2
|
+
|
|
1
3
|
// vim: ts=4:sw=4
|
|
2
4
|
|
|
3
5
|
const BaseKeyType = require('./base_key_type');
|
|
@@ -170,9 +172,7 @@ const migrations = [{
|
|
|
170
172
|
} else {
|
|
171
173
|
for (const key in sessions) {
|
|
172
174
|
if (sessions[key].indexInfo.closed === -1) {
|
|
173
|
-
|
|
174
|
-
data.registrationId, 'for open session version',
|
|
175
|
-
data.version);
|
|
175
|
+
_whaErr('V1 session storage migration error: registrationId ' + data.registrationId + ' for open session version ' + data.version);
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
}
|
|
@@ -190,7 +190,7 @@ class SessionRecord {
|
|
|
190
190
|
let run = (data.version === undefined);
|
|
191
191
|
for (let i = 0; i < migrations.length; ++i) {
|
|
192
192
|
if (run) {
|
|
193
|
-
|
|
193
|
+
_whaInfo('Migrating session to: ' + migrations[i].version);
|
|
194
194
|
migrations[i].migrate(data);
|
|
195
195
|
} else if (migrations[i].version === data.version) {
|
|
196
196
|
run = true;
|
|
@@ -267,18 +267,18 @@ class SessionRecord {
|
|
|
267
267
|
|
|
268
268
|
closeSession(session) {
|
|
269
269
|
if (this.isClosed(session)) {
|
|
270
|
-
|
|
270
|
+
_whaWarn('Session already closed');
|
|
271
271
|
return;
|
|
272
272
|
}
|
|
273
|
-
|
|
273
|
+
_whaInfo('Closing session');
|
|
274
274
|
session.indexInfo.closed = Date.now();
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
openSession(session) {
|
|
278
278
|
if (!this.isClosed(session)) {
|
|
279
|
-
|
|
279
|
+
_whaWarn('Session already open');
|
|
280
280
|
}
|
|
281
|
-
|
|
281
|
+
_whaInfo('Opening session');
|
|
282
282
|
session.indexInfo.closed = -1;
|
|
283
283
|
}
|
|
284
284
|
|
|
@@ -298,7 +298,7 @@ class SessionRecord {
|
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
300
|
if (oldestKey) {
|
|
301
|
-
|
|
301
|
+
_whaInfo('Removing old closed session');
|
|
302
302
|
delete this.sessions[oldestKey];
|
|
303
303
|
} else {
|
|
304
304
|
throw new Error('Corrupt sessions object');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whalibmob",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.31",
|
|
4
4
|
"description": "WhatsApp library for interaction with WhatsApp Mobile API no web",
|
|
5
5
|
"author": "Kunboruto20",
|
|
6
6
|
"main": "index.js",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"protobufjs": "^6.11.4",
|
|
47
47
|
"uuid": "^11.1.0",
|
|
48
48
|
"async-mutex": "^0.5.0",
|
|
49
|
-
"@cacheable/node-cache": "^3.0.1"
|
|
49
|
+
"@cacheable/node-cache": "^3.0.1",
|
|
50
|
+
"pino": "^9.0.0"
|
|
50
51
|
}
|
|
51
52
|
}
|