whalibmob 5.5.34 → 5.5.36

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 CHANGED
@@ -117,8 +117,11 @@ class WhalibmobClient extends EventEmitter {
117
117
  this._signal = null;
118
118
  this._devMgr = null;
119
119
  this._sessionDir = opts.sessionDir || process.env.HOME + '/.waSession';
120
- this._pingTimer = null;
121
- this._keepTimer = null;
120
+ this._pingTimer = null;
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)
122
125
  this._connected = false;
123
126
  this._reconnecting = false;
124
127
  this._reconnectTry = 0;
@@ -259,6 +262,8 @@ class WhalibmobClient extends EventEmitter {
259
262
 
260
263
  _onOpen(successNode) {
261
264
  this._connected = true;
265
+ this._watchdogLastPingSentAt = 0;
266
+ this._watchdogPongReceived = true;
262
267
  this._startTimers();
263
268
 
264
269
  // ── Feature 1: Parse <success> node ──────────────────────────────────────
@@ -495,6 +500,9 @@ class WhalibmobClient extends EventEmitter {
495
500
 
496
501
  this._keepTimer = setInterval(() => {
497
502
  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;
498
506
  this._socket.sendNode(new BinaryNode('iq', {
499
507
  id: this._genMsgId(),
500
508
  to: 's.whatsapp.net',
@@ -504,18 +512,21 @@ class WhalibmobClient extends EventEmitter {
504
512
  }
505
513
  }, KEEPALIVE_INTERVAL);
506
514
 
507
- // Watchdog: if no data arrived from WhatsApp in 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.
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 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.
510
520
  const WATCHDOG_INTERVAL = KEEPALIVE_INTERVAL * 2 + 5000;
511
521
  this._watchdogTimer = setInterval(() => {
512
522
  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 (_) {}
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 (_) {}
519
530
  }
520
531
  }, KEEPALIVE_INTERVAL);
521
532
  }
@@ -530,6 +541,8 @@ class WhalibmobClient extends EventEmitter {
530
541
 
531
542
  _onNode(node) {
532
543
  if (!node || !node.description) return;
544
+ // Any node from the server means the connection is alive — reset watchdog state.
545
+ this._watchdogPongReceived = true;
533
546
  const tag = node.description;
534
547
  // Debug: log every node received
535
548
  _whaDbg('[DBG] _onNode tag=' + tag + ' attrs=' + JSON.stringify(node.attrs || {}));
@@ -412,34 +412,45 @@ class DeviceManager {
412
412
  return jids.length > 0 ? jids : phones.map(p => makeDeviceJid(p, 0));
413
413
  }
414
414
 
415
- // ─── FIXED usync IQ — corrects 4 bugs in the original implementation ────────
415
+ // ─── usync IQ — builds the device-list query exactly as WhatsApp APK does ────
416
416
  //
417
- // Bug 1 Wrong <list> node format:
418
- // Was: <user><contact>+phone</contact></user>
419
- // Fix: <user jid="phone@s.whatsapp.net"/> (jid ATTRIBUTE, not contact child)
417
+ // APK-confirmed format (extracted from WhatsApp DEX string tables):
420
418
  //
421
- // Bug 2 Wrong <query><devices> node:
422
- // Was: <devices version="2"><device jid="phone@s.whatsapp.net"/></devices>
423
- // Fix: <devices version="2"/> (NO device children in query)
419
+ // <iq to="s.whatsapp.net" type="get" xmlns="usync">
420
+ // <usync sid="..." mode="query" last="true" index="0" context="message">
421
+ // <query>
422
+ // <devices version="2" device_orientation="0" fetch_options="5"/>
423
+ // <lid/>
424
+ // </query>
425
+ // <list>
426
+ // <user><contact>+phone</contact></user> ← Buffer, not jid attr string
427
+ // </list>
428
+ // <side_list/>
429
+ // </usync>
430
+ // </iq>
424
431
  //
425
- // Bug 3 Missing <side_list/> node:
426
- // Was: (absent)
427
- // Fix: <side_list/> (required for LID device discovery)
432
+ // Critical fixes vs original whalibmob implementation:
428
433
  //
429
- // Bug 4Wrong response parser:
430
- // Was: deep walk looking for <device jid="..."> attrs
431
- // Fix: navigate usync list user[jid=...] devices device-list → device[id=N]
432
- // Server returns <device id="0"/> NOT <device jid="..."/>
433
- // The phone/LID identifier is on the PARENT <user jid="..."> node, not on device.
434
+ // Fix Afetch_options="5" MISSING from <devices> node:
435
+ // Without fetch_options="5", the WA server returns ONLY device 0 (primary phone).
436
+ // fetch_options="5" instructs the server to return ALL linked devices.
437
+ // device_orientation="0" is also required by the server.
438
+ //
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.
434
443
  //
435
444
  async _doUsyncIq(phones) {
436
445
  const iqId = this._client._genMsgId();
437
446
  const sid = this._client._genMsgId();
438
447
 
439
- // FIX Bug 1: jid attribute on <user>, not a <contact> child node
440
- // FIX Bug 2: <devices version="2"/> with NO children
448
+ // Fix B: <user><contact>+phone</contact></user> matches APK exactly.
449
+ // Buffer.from ensures binary encoding (not raw UTF-8 string attribute).
441
450
  const listChildren = phones.map(p =>
442
- new BinaryNode('user', { jid: `${p}@s.whatsapp.net` }, null)
451
+ new BinaryNode('user', {}, [
452
+ new BinaryNode('contact', {}, Buffer.from('+' + p, 'utf8'))
453
+ ])
443
454
  );
444
455
 
445
456
  const iqNode = new BinaryNode('iq',
@@ -449,7 +460,9 @@ class DeviceManager {
449
460
  [
450
461
  new BinaryNode('query', {},
451
462
  [
452
- new BinaryNode('devices', { version: '2' }, null),
463
+ // Fix A: fetch_options="5" + device_orientation="0" — required for
464
+ // the server to return ALL linked devices, not just device 0.
465
+ new BinaryNode('devices', { version: '2', device_orientation: '0', fetch_options: '5' }, null),
453
466
  new BinaryNode('lid', {}, null)
454
467
  ]
455
468
  ),
@@ -771,6 +784,13 @@ class DeviceManager {
771
784
  const iqId = this._client._genMsgId();
772
785
  const sid = this._client._genMsgId();
773
786
 
787
+ // Build proper JID object for binary encoding (JID_PAIR format, not raw string)
788
+ const jidStr = typeof jid === 'string' ? jid : String(jid);
789
+ const atIdx = jidStr.indexOf('@');
790
+ const jidUser = atIdx >= 0 ? jidStr.slice(0, atIdx) : jidStr;
791
+ const jidSrv = atIdx >= 0 ? jidStr.slice(atIdx + 1) : 'lid';
792
+ const jidObj = { user: jidUser, server: jidSrv, toString() { return jidStr; } };
793
+
774
794
  const iqNode = new BinaryNode('iq',
775
795
  { id: iqId, to: 's.whatsapp.net', type: 'get', xmlns: 'usync' },
776
796
  [new BinaryNode('usync',
@@ -778,12 +798,13 @@ class DeviceManager {
778
798
  [
779
799
  new BinaryNode('query', {},
780
800
  [
781
- new BinaryNode('devices', { version: '2' }, null),
801
+ // Fix A applied here too: fetch_options="5" + device_orientation="0"
802
+ new BinaryNode('devices', { version: '2', device_orientation: '0', fetch_options: '5' }, null),
782
803
  new BinaryNode('lid', {}, null)
783
804
  ]
784
805
  ),
785
806
  new BinaryNode('list', {},
786
- [new BinaryNode('user', { jid }, null)]
807
+ [new BinaryNode('user', { jid: jidObj }, null)]
787
808
  ),
788
809
  new BinaryNode('side_list', {}, null)
789
810
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whalibmob",
3
- "version": "5.5.34",
3
+ "version": "5.5.36",
4
4
  "description": "WhatsApp library for interaction with WhatsApp Mobile API no web",
5
5
  "author": "Kunboruto20",
6
6
  "main": "index.js",