whalibmob 5.5.36 → 5.5.37
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 +2 -31
- package/lib/DeviceManager.js +72 -69
- package/package.json +1 -1
package/lib/Client.js
CHANGED
|
@@ -119,9 +119,6 @@ class WhalibmobClient extends EventEmitter {
|
|
|
119
119
|
this._sessionDir = opts.sessionDir || process.env.HOME + '/.waSession';
|
|
120
120
|
this._pingTimer = null;
|
|
121
121
|
this._keepTimer = null;
|
|
122
|
-
this._watchdogTimer = null;
|
|
123
|
-
this._watchdogLastPingSentAt = 0; // timestamp when last keepalive ping was sent
|
|
124
|
-
this._watchdogPongReceived = true; // true = server responded after last ping (or no ping sent yet)
|
|
125
122
|
this._connected = false;
|
|
126
123
|
this._reconnecting = false;
|
|
127
124
|
this._reconnectTry = 0;
|
|
@@ -262,8 +259,6 @@ class WhalibmobClient extends EventEmitter {
|
|
|
262
259
|
|
|
263
260
|
_onOpen(successNode) {
|
|
264
261
|
this._connected = true;
|
|
265
|
-
this._watchdogLastPingSentAt = 0;
|
|
266
|
-
this._watchdogPongReceived = true;
|
|
267
262
|
this._startTimers();
|
|
268
263
|
|
|
269
264
|
// ── Feature 1: Parse <success> node ──────────────────────────────────────
|
|
@@ -500,9 +495,6 @@ class WhalibmobClient extends EventEmitter {
|
|
|
500
495
|
|
|
501
496
|
this._keepTimer = setInterval(() => {
|
|
502
497
|
if (this._socket && this._connected) {
|
|
503
|
-
// Record the time we sent this ping so the watchdog can check for missing pongs.
|
|
504
|
-
this._watchdogLastPingSentAt = Date.now();
|
|
505
|
-
this._watchdogPongReceived = false;
|
|
506
498
|
this._socket.sendNode(new BinaryNode('iq', {
|
|
507
499
|
id: this._genMsgId(),
|
|
508
500
|
to: 's.whatsapp.net',
|
|
@@ -511,38 +503,17 @@ class WhalibmobClient extends EventEmitter {
|
|
|
511
503
|
}, [new BinaryNode('ping', {}, null)]));
|
|
512
504
|
}
|
|
513
505
|
}, KEEPALIVE_INTERVAL);
|
|
514
|
-
|
|
515
|
-
// Watchdog: detects a truly dead TCP connection where the OS never emits 'close'
|
|
516
|
-
// (common with NAT/firewall drops). Logic: if we sent a keepalive ping and the
|
|
517
|
-
// server did NOT reply within 2× keepalive window, the connection is dead.
|
|
518
|
-
// NOTE: idleness alone (bot sleeping between sends) is NOT treated as dead —
|
|
519
|
-
// only an unanswered ping triggers the close, so any delay is safe.
|
|
520
|
-
const WATCHDOG_INTERVAL = KEEPALIVE_INTERVAL * 2 + 5000;
|
|
521
|
-
this._watchdogTimer = setInterval(() => {
|
|
522
|
-
if (!this._socket || !this._connected) return;
|
|
523
|
-
if (!this._watchdogPongReceived &&
|
|
524
|
-
this._watchdogLastPingSentAt > 0 &&
|
|
525
|
-
Date.now() - this._watchdogLastPingSentAt > WATCHDOG_INTERVAL) {
|
|
526
|
-
_whaDbg('[DBG] WATCHDOG: keepalive ping sent ' +
|
|
527
|
-
Math.round((Date.now() - this._watchdogLastPingSentAt) / 1000) +
|
|
528
|
-
's ago with no server response — force-closing dead connection');
|
|
529
|
-
try { this._socket.close(); } catch (_) {}
|
|
530
|
-
}
|
|
531
|
-
}, KEEPALIVE_INTERVAL);
|
|
532
506
|
}
|
|
533
507
|
|
|
534
508
|
_stopTimers() {
|
|
535
|
-
if (this._pingTimer)
|
|
536
|
-
if (this._keepTimer)
|
|
537
|
-
if (this._watchdogTimer) { clearInterval(this._watchdogTimer); this._watchdogTimer = null; }
|
|
509
|
+
if (this._pingTimer) { clearInterval(this._pingTimer); this._pingTimer = null; }
|
|
510
|
+
if (this._keepTimer) { clearInterval(this._keepTimer); this._keepTimer = null; }
|
|
538
511
|
}
|
|
539
512
|
|
|
540
513
|
// ─── Node dispatch ────────────────────────────────────────────────────────
|
|
541
514
|
|
|
542
515
|
_onNode(node) {
|
|
543
516
|
if (!node || !node.description) return;
|
|
544
|
-
// Any node from the server means the connection is alive — reset watchdog state.
|
|
545
|
-
this._watchdogPongReceived = true;
|
|
546
517
|
const tag = node.description;
|
|
547
518
|
// Debug: log every node received
|
|
548
519
|
_whaDbg('[DBG] _onNode tag=' + tag + ' attrs=' + JSON.stringify(node.attrs || {}));
|
package/lib/DeviceManager.js
CHANGED
|
@@ -437,33 +437,33 @@ class DeviceManager {
|
|
|
437
437
|
// device_orientation="0" is also required by the server.
|
|
438
438
|
//
|
|
439
439
|
// Fix B — User list format: use <contact>+phone</contact> as Buffer content.
|
|
440
|
-
// The BinaryNode encoder encodes string attributes as raw UTF-8 (BINARY_8 tag),
|
|
441
|
-
// NOT as JID_PAIR binary format. The APK sends phone as Buffer content of a
|
|
442
|
-
// <contact> child node — this is what the server actually expects in the list.
|
|
443
|
-
//
|
|
444
440
|
async _doUsyncIq(phones) {
|
|
445
441
|
const iqId = this._client._genMsgId();
|
|
446
442
|
const sid = this._client._genMsgId();
|
|
447
443
|
|
|
448
|
-
//
|
|
449
|
-
//
|
|
444
|
+
// User list: <user><contact>+phone</contact></user>
|
|
445
|
+
// This is the ONLY format the server responds to for phone-based usync.
|
|
446
|
+
// The <user jid="..."/> attribute format is silently ignored by the server.
|
|
450
447
|
const listChildren = phones.map(p =>
|
|
451
448
|
new BinaryNode('user', {}, [
|
|
452
449
|
new BinaryNode('contact', {}, Buffer.from('+' + p, 'utf8'))
|
|
453
450
|
])
|
|
454
451
|
);
|
|
455
452
|
|
|
453
|
+
// context="interactive" is REQUIRED.
|
|
454
|
+
// The server ignores context="message" usync IQs entirely (never replies).
|
|
455
|
+
// Query includes <devices>, <lid>, AND <contact> so the server returns
|
|
456
|
+
// the full device list + LID mapping + contact presence in one round-trip.
|
|
456
457
|
const iqNode = new BinaryNode('iq',
|
|
457
458
|
{ id: iqId, to: 's.whatsapp.net', type: 'get', xmlns: 'usync' },
|
|
458
459
|
[new BinaryNode('usync',
|
|
459
|
-
{ sid, mode: 'query', last: 'true', index: '0', context: '
|
|
460
|
+
{ sid, mode: 'query', last: 'true', index: '0', context: 'interactive' },
|
|
460
461
|
[
|
|
461
462
|
new BinaryNode('query', {},
|
|
462
463
|
[
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
new BinaryNode('
|
|
466
|
-
new BinaryNode('lid', {}, null)
|
|
464
|
+
new BinaryNode('devices', { version: '2' }, null),
|
|
465
|
+
new BinaryNode('lid', {}, null),
|
|
466
|
+
new BinaryNode('contact', {}, null)
|
|
467
467
|
]
|
|
468
468
|
),
|
|
469
469
|
new BinaryNode('list', {}, listChildren),
|
|
@@ -472,27 +472,18 @@ class DeviceManager {
|
|
|
472
472
|
)]
|
|
473
473
|
);
|
|
474
474
|
|
|
475
|
-
_whaDbg('[
|
|
475
|
+
_whaDbg('[USYNC_IQ] phones=[' + phones.join(',') + ']');
|
|
476
476
|
|
|
477
477
|
const response = await this._client._sendIq(iqNode).catch(err => {
|
|
478
|
-
_whaDbg('[
|
|
478
|
+
_whaDbg('[USYNC_ERR] ' + (err && err.message));
|
|
479
479
|
return null;
|
|
480
480
|
});
|
|
481
481
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
// <device-list>
|
|
488
|
-
// <device id="0"/>
|
|
489
|
-
// <device id="2"/>
|
|
490
|
-
// </device-list>
|
|
491
|
-
// </devices>
|
|
492
|
-
// </user>
|
|
493
|
-
// </list>
|
|
494
|
-
// </usync>
|
|
495
|
-
if (response) {
|
|
482
|
+
if (!response) {
|
|
483
|
+
_whaDbg('[USYNC_NULL_RESP]');
|
|
484
|
+
// Timed out — fall back to contact usync for LID mapping only.
|
|
485
|
+
await this._doContactUsync(phones).catch(() => {});
|
|
486
|
+
} else {
|
|
496
487
|
const usyncNode = findChild(response, 'usync');
|
|
497
488
|
const listNode = usyncNode
|
|
498
489
|
? findChild(usyncNode, 'list')
|
|
@@ -508,37 +499,47 @@ class DeviceManager {
|
|
|
508
499
|
const isLidUser = String(userJid).endsWith('@lid');
|
|
509
500
|
const { user: rawUser } = stripUser(String(userJid));
|
|
510
501
|
|
|
511
|
-
// Determine phone-based cache key (always a phone number, never a LID)
|
|
512
502
|
let cachePhone;
|
|
513
503
|
if (isLidUser) {
|
|
514
|
-
//
|
|
504
|
+
// Server returned a LID JID — look up the corresponding phone number.
|
|
515
505
|
const pn = this._client._lidToPn && this._client._lidToPn.get(rawUser);
|
|
516
506
|
if (pn) {
|
|
517
507
|
cachePhone = pn;
|
|
518
508
|
} else if (phones.length === 1) {
|
|
519
|
-
// Single phone queried — this LID must belong to it
|
|
520
509
|
cachePhone = phones[0];
|
|
521
510
|
if (this._client._lidToPn) this._client._lidToPn.set(rawUser, phones[0]);
|
|
522
511
|
if (this._client._pnToLid) this._client._pnToLid.set(phones[0], rawUser);
|
|
523
512
|
} else {
|
|
524
|
-
cachePhone = rawUser; // last resort
|
|
513
|
+
cachePhone = rawUser; // last resort
|
|
525
514
|
}
|
|
526
|
-
_whaDbg('[
|
|
515
|
+
_whaDbg('[USYNC_LID_USER] jid=' + userJid + ' → phone=' + cachePhone);
|
|
527
516
|
} else {
|
|
528
|
-
//
|
|
517
|
+
// Server returned a phone JID (phone@s.whatsapp.net).
|
|
529
518
|
cachePhone = rawUser;
|
|
530
519
|
|
|
531
|
-
//
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
520
|
+
// The LID is in a <lid val={user:"NNNN",...}> CHILD NODE, NOT an attr.
|
|
521
|
+
// Extract and populate both directions of the PN↔LID mapping, and
|
|
522
|
+
// pre-populate the lid: cache key so bulkEnsureSessionsForLid can
|
|
523
|
+
// skip a second IQ for this recipient.
|
|
524
|
+
const lidChildNode = findChild(userNode, 'lid');
|
|
525
|
+
const lidVal = lidChildNode && lidChildNode.attrs && lidChildNode.attrs.val;
|
|
526
|
+
if (lidVal) {
|
|
527
|
+
const lidUser = lidVal.user
|
|
528
|
+
? String(lidVal.user)
|
|
529
|
+
: String(lidVal).split('@')[0].split(':')[0];
|
|
530
|
+
if (lidUser) {
|
|
531
|
+
if (this._client._lidToPn) this._client._lidToPn.set(lidUser, cachePhone);
|
|
532
|
+
if (this._client._pnToLid) this._client._pnToLid.set(cachePhone, lidUser);
|
|
533
|
+
const store = this._client._signal && this._client._signal.store;
|
|
534
|
+
if (store && store.setLidMapping) store.setLidMapping(cachePhone, lidUser);
|
|
535
|
+
_whaDbg('[USYNC_LID_MAP] phone=' + cachePhone + ' ↔ lid=' + lidUser);
|
|
536
|
+
this._lidForPhone = this._lidForPhone || new Map();
|
|
537
|
+
this._lidForPhone.set(cachePhone, lidUser);
|
|
538
|
+
}
|
|
538
539
|
}
|
|
539
540
|
}
|
|
540
541
|
|
|
541
|
-
//
|
|
542
|
+
// Parse device list: user → devices → device-list → device[id=N]
|
|
542
543
|
const devicesNode = findChild(userNode, 'devices');
|
|
543
544
|
const deviceListNode = devicesNode ? findChild(devicesNode, 'device-list') : null;
|
|
544
545
|
|
|
@@ -547,37 +548,38 @@ class DeviceManager {
|
|
|
547
548
|
if (deviceListNode && Array.isArray(deviceListNode.content)) {
|
|
548
549
|
for (const devNode of deviceListNode.content) {
|
|
549
550
|
if (!devNode || devNode.description !== 'device') continue;
|
|
550
|
-
// FIX: server uses `id` attribute (e.g. id="0"), NOT `jid` attribute
|
|
551
551
|
const devId = devNode.attrs && devNode.attrs.id != null
|
|
552
552
|
? parseInt(String(devNode.attrs.id), 10)
|
|
553
553
|
: 0;
|
|
554
554
|
this._dcAdd(cachePhone, devId);
|
|
555
555
|
}
|
|
556
|
-
|
|
557
|
-
|
|
556
|
+
|
|
557
|
+
// Mirror device IDs to the lid: cache key so _doUsyncIqByJid is
|
|
558
|
+
// skipped when bulkEnsureSessionsForLid is later called for this LID.
|
|
559
|
+
const knownLid = this._lidForPhone && this._lidForPhone.get(cachePhone);
|
|
560
|
+
if (knownLid) {
|
|
561
|
+
const devIds = this._dcGet(cachePhone) || [];
|
|
562
|
+
this._dcSet('lid:' + knownLid, devIds.slice());
|
|
563
|
+
_whaDbg('[USYNC_LID_CACHE] lid=' + knownLid + ' ids=[' + devIds.join(',') + ']');
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
_whaDbg('[USYNC_DEVICES] phone=' + cachePhone +
|
|
567
|
+
' ids=[' + (this._dcGet(cachePhone) || []).join(',') + ']');
|
|
558
568
|
} else {
|
|
559
|
-
// No device-list in response — fall back to device 0
|
|
560
569
|
this._dcAdd(cachePhone, 0);
|
|
561
|
-
_whaDbg('[
|
|
570
|
+
_whaDbg('[USYNC_NO_DEVLIST] phone=' + cachePhone + ' → device 0 only');
|
|
562
571
|
}
|
|
563
572
|
}
|
|
564
573
|
} else {
|
|
565
|
-
_whaDbg('[
|
|
574
|
+
_whaDbg('[USYNC_RESP_NO_LIST]');
|
|
566
575
|
}
|
|
567
|
-
} else {
|
|
568
|
-
_whaDbg('[DBG] USYNC_NULL_RESP\n');
|
|
569
|
-
// Device usync timed out — fall back to a contact usync (query/contact).
|
|
570
|
-
// This DOES receive a server response and resolves the phone → LID mapping.
|
|
571
|
-
// Once _pnToLid is populated here, _sendDMMessage (after its await) will
|
|
572
|
-
// use the LID JID for routing instead of the stale phone JID.
|
|
573
|
-
await this._doContactUsync(phones).catch(() => {});
|
|
574
576
|
}
|
|
575
577
|
|
|
576
|
-
//
|
|
578
|
+
// Fallback: any phone not resolved → device 0
|
|
577
579
|
for (const p of phones) {
|
|
578
580
|
if (!this._dcHas(p)) {
|
|
579
581
|
this._dcSet(p, [0]);
|
|
580
|
-
_whaDbg('[
|
|
582
|
+
_whaDbg('[USYNC_FALLBACK] phone=' + p);
|
|
581
583
|
}
|
|
582
584
|
}
|
|
583
585
|
this._scheduleSave();
|
|
@@ -725,15 +727,15 @@ class DeviceManager {
|
|
|
725
727
|
)]
|
|
726
728
|
);
|
|
727
729
|
|
|
728
|
-
_whaDbg('[
|
|
730
|
+
_whaDbg('[CONTACT_USYNC] phones=[' + phones.join(',') + ']');
|
|
729
731
|
|
|
730
732
|
const response = await this._client._sendIq(iqNode).catch(err => {
|
|
731
|
-
_whaDbg('[
|
|
733
|
+
_whaDbg('[CONTACT_USYNC_ERR] ' + (err && err.message));
|
|
732
734
|
return null;
|
|
733
735
|
});
|
|
734
736
|
|
|
735
737
|
if (!response) {
|
|
736
|
-
_whaDbg('[
|
|
738
|
+
_whaDbg('[CONTACT_USYNC_NULL]');
|
|
737
739
|
return;
|
|
738
740
|
}
|
|
739
741
|
|
|
@@ -794,12 +796,13 @@ class DeviceManager {
|
|
|
794
796
|
const iqNode = new BinaryNode('iq',
|
|
795
797
|
{ id: iqId, to: 's.whatsapp.net', type: 'get', xmlns: 'usync' },
|
|
796
798
|
[new BinaryNode('usync',
|
|
797
|
-
{ sid, mode: 'query', last: 'true', index: '0', context: '
|
|
799
|
+
{ sid, mode: 'query', last: 'true', index: '0', context: 'interactive' },
|
|
798
800
|
[
|
|
799
801
|
new BinaryNode('query', {},
|
|
800
802
|
[
|
|
801
|
-
//
|
|
802
|
-
|
|
803
|
+
// context="interactive" is REQUIRED — server silently ignores
|
|
804
|
+
// "message" context usync IQs (same as _doUsyncIq).
|
|
805
|
+
new BinaryNode('devices', { version: '2' }, null),
|
|
803
806
|
new BinaryNode('lid', {}, null)
|
|
804
807
|
]
|
|
805
808
|
),
|
|
@@ -811,10 +814,10 @@ class DeviceManager {
|
|
|
811
814
|
)]
|
|
812
815
|
);
|
|
813
816
|
|
|
814
|
-
_whaDbg('[
|
|
817
|
+
_whaDbg('[USYNC_LID_IQ] jid=' + jid);
|
|
815
818
|
|
|
816
819
|
const response = await this._client._sendIq(iqNode).catch(err => {
|
|
817
|
-
_whaDbg('[
|
|
820
|
+
_whaDbg('[USYNC_LID_ERR] ' + (err && err.message));
|
|
818
821
|
return null;
|
|
819
822
|
});
|
|
820
823
|
|
|
@@ -838,19 +841,19 @@ class DeviceManager {
|
|
|
838
841
|
? parseInt(String(devNode.attrs.id), 10) : 0;
|
|
839
842
|
this._dcAdd(cacheKey, devId);
|
|
840
843
|
}
|
|
841
|
-
_whaDbg('[
|
|
842
|
-
' ids=[' + (this._dcGet(cacheKey) || []).join(',') + ']
|
|
844
|
+
_whaDbg('[USYNC_LID_DEVICES] jid=' + jid +
|
|
845
|
+
' ids=[' + (this._dcGet(cacheKey) || []).join(',') + ']');
|
|
843
846
|
} else {
|
|
844
847
|
this._dcAdd(cacheKey, 0);
|
|
845
|
-
_whaDbg('[
|
|
848
|
+
_whaDbg('[USYNC_LID_NO_DEVLIST] jid=' + jid + ' → device 0 only');
|
|
846
849
|
}
|
|
847
850
|
}
|
|
848
851
|
} else {
|
|
849
|
-
_whaDbg('[
|
|
852
|
+
_whaDbg('[USYNC_LID_NO_LIST] jid=' + jid);
|
|
850
853
|
this._dcAdd(cacheKey, 0);
|
|
851
854
|
}
|
|
852
855
|
} else {
|
|
853
|
-
_whaDbg('[
|
|
856
|
+
_whaDbg('[USYNC_LID_NULL_RESP] jid=' + jid);
|
|
854
857
|
this._dcAdd(cacheKey, 0);
|
|
855
858
|
}
|
|
856
859
|
this._scheduleSave();
|