whalibmob 5.5.72 → 5.5.75
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/BinaryNode.js +7 -1
- package/lib/DeviceManager.js +49 -15
- package/lib/messages/MessageSender.js +45 -0
- package/lib/signal/SignalProtocol.js +8 -0
- package/package.json +1 -1
package/lib/BinaryNode.js
CHANGED
|
@@ -310,9 +310,15 @@ class NodeDecoder {
|
|
|
310
310
|
// that does not exist, so device queries for it came back empty, no Signal
|
|
311
311
|
// session was ever built, and group sends were rejected with ack 479.
|
|
312
312
|
const server = _serverForAgent(agent);
|
|
313
|
+
// The device must survive stringification. Everything downstream keys off
|
|
314
|
+
// the string form: fetchBundles maps bundles by it, so five devices all
|
|
315
|
+
// rendering as "user@server" collapsed into one entry and left the rest
|
|
316
|
+
// without a session; and jidToAddress parses the ":device" out of it, so
|
|
317
|
+
// dropping it addressed every Signal session as device 0 — which is why a
|
|
318
|
+
// message from a linked device could not be decrypted either.
|
|
313
319
|
return {
|
|
314
320
|
user, agent, device, server,
|
|
315
|
-
toString() { return `${user}@${server}`; }
|
|
321
|
+
toString() { return device ? `${user}:${device}@${server}` : `${user}@${server}`; }
|
|
316
322
|
};
|
|
317
323
|
}
|
|
318
324
|
}
|
package/lib/DeviceManager.js
CHANGED
|
@@ -44,12 +44,18 @@ function jidStrToObj(jidStr) {
|
|
|
44
44
|
if (server === 's.whatsapp.net' && device > 0) {
|
|
45
45
|
return { user, agent: 0, device, server, toString() { return self; } };
|
|
46
46
|
}
|
|
47
|
-
// @lid multi-device (e.g. "112713111982325:2@lid"):
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
47
|
+
// @lid multi-device (e.g. "112713111982325:2@lid"): AD_JID with the domain in
|
|
48
|
+
// the agent byte — 1 for lid, matching Baileys' writeJid, which emits AD_JID
|
|
49
|
+
// with domainType whenever a device is present.
|
|
50
|
+
//
|
|
51
|
+
// This previously packed the device into the user string as a JID_PAIR
|
|
52
|
+
// ("112713111982325:2"), a workaround from when the encoder could not express
|
|
53
|
+
// a LID agent at all and would otherwise have flattened the JID to device 0.
|
|
54
|
+
// The server does not recognise that shape: a prekey IQ addressed with it got
|
|
55
|
+
// no reply whatsoever and timed out, so no session was ever built for the
|
|
56
|
+
// contact's linked devices and sends failed with NO_OPEN_SESSION.
|
|
51
57
|
if (server === 'lid' && device > 0) {
|
|
52
|
-
return { user:
|
|
58
|
+
return { user, agent: 1, device, server, toString() { return self; } };
|
|
53
59
|
}
|
|
54
60
|
// JID_PAIR: used for @lid device-0, @g.us, and primary (device-0) @s.whatsapp.net
|
|
55
61
|
return { user, server, toString() { return self; } };
|
|
@@ -749,8 +755,37 @@ class DeviceManager {
|
|
|
749
755
|
}
|
|
750
756
|
|
|
751
757
|
if (allowPkmsg && fetchJids.length > 0) {
|
|
752
|
-
|
|
753
|
-
|
|
758
|
+
// Ask for the bundles by phone JID. The encrypt IQ is not answered at all
|
|
759
|
+
// when its <user> entries are @lid — it just times out — while the same
|
|
760
|
+
// request in phone form comes back in full. Translate through the known
|
|
761
|
+
// LID→PN mapping, keeping a link back so each bundle is installed under
|
|
762
|
+
// the LID address the participants node will use.
|
|
763
|
+
const pnForLid = this._client._lidToPn && this._client._lidToPn.get(lidUser);
|
|
764
|
+
const wireToLid = new Map();
|
|
765
|
+
let wireJids = fetchJids;
|
|
766
|
+
if (pnForLid) {
|
|
767
|
+
wireJids = fetchJids.map(j => {
|
|
768
|
+
const at = j.indexOf('@');
|
|
769
|
+
const base = at >= 0 ? j.slice(0, at) : j;
|
|
770
|
+
const colon = base.indexOf(':');
|
|
771
|
+
const dev = colon >= 0 ? (parseInt(base.slice(colon + 1), 10) || 0) : 0;
|
|
772
|
+
const wire = makeDeviceJid(pnForLid, dev, 's.whatsapp.net');
|
|
773
|
+
wireToLid.set(wire, j);
|
|
774
|
+
return wire;
|
|
775
|
+
});
|
|
776
|
+
_whaDbg('[DBG] LID_BUNDLE_VIA_PN lid=' + lidUser + ' pn=' + pnForLid +
|
|
777
|
+
' jids=[' + wireJids.join(',') + ']\n');
|
|
778
|
+
} else {
|
|
779
|
+
_whaDbg('[DBG] LID_BUNDLE_NO_PN lid=' + lidUser + ' — requesting by LID\n');
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const bundles = await this.fetchBundles(wireJids);
|
|
783
|
+
for (const [wireJid, bundle] of bundles) {
|
|
784
|
+
// Install under the @lid address: that is what the outgoing
|
|
785
|
+
// participants node — and therefore the encrypt step — looks the
|
|
786
|
+
// session up by. Stored under the phone user it is a different Signal
|
|
787
|
+
// address and would never be found.
|
|
788
|
+
const jid = wireToLid.get(wireJid) || wireJid;
|
|
754
789
|
try {
|
|
755
790
|
await signalProto.buildSessionFromBundle(jid, bundle);
|
|
756
791
|
// ── CRITICAL: remap bundle JID back to @lid format ────────────────────
|
|
@@ -760,14 +795,13 @@ class DeviceManager {
|
|
|
760
795
|
// nodes to also use @lid format — mixing @s.whatsapp.net participants with
|
|
761
796
|
// a @lid routing address causes the server to close the connection.
|
|
762
797
|
//
|
|
763
|
-
//
|
|
764
|
-
//
|
|
765
|
-
//
|
|
766
|
-
//
|
|
767
|
-
//
|
|
768
|
-
//
|
|
769
|
-
//
|
|
770
|
-
// "112713111982325:2@s.whatsapp.net" → "112713111982325:2@lid"
|
|
798
|
+
// A Signal address is (user, device) with the @server stripped — but
|
|
799
|
+
// the user of a LID and the user of a phone JID are different strings,
|
|
800
|
+
// so a session is NOT reachable under both. That assumption is what
|
|
801
|
+
// left sends failing: bundles fetched in phone form were stored under
|
|
802
|
+
// the phone user while the participants node looked them up by LID.
|
|
803
|
+
// The session is installed under the @lid address above; this only
|
|
804
|
+
// normalises the JID pushed into readyJids.
|
|
771
805
|
const jidBase = jid.replace(/@(?:s\.whatsapp\.net|lid)$/, '');
|
|
772
806
|
const lidJidForDevice = jidBase + '@lid';
|
|
773
807
|
readyJids.push(lidJidForDevice);
|
|
@@ -554,6 +554,21 @@ class MessageSender {
|
|
|
554
554
|
return this._sendGroupMessage(toJid, msgId, plaintext, mediaType,
|
|
555
555
|
Object.assign({}, options, { _479retry: true }));
|
|
556
556
|
}
|
|
557
|
+
// No session could be built for a single member — usually a prekey IQ
|
|
558
|
+
// that timed out, leaving the device list cached with nothing behind
|
|
559
|
+
// it. Purge the cached devices and sessions so the retry re-fetches
|
|
560
|
+
// bundles and opens fresh pkmsg sessions instead of failing again.
|
|
561
|
+
if (err && /NO_OPEN_SESSION/.test(err.message) && !options._noSessionRetry) {
|
|
562
|
+
_whaDbg('[DBG] NO_OPEN_SESSION group ' + toJid + ' — forcing fresh pkmsg\n');
|
|
563
|
+
await this._forceSessionRebuild(
|
|
564
|
+
(this._client._getGroupMembers ? this._client._getGroupMembers(toJid) : [])
|
|
565
|
+
);
|
|
566
|
+
if (this._signal && this._signal.senderKeyStore) {
|
|
567
|
+
this._signal.senderKeyStore.invalidateSKDM(toJid);
|
|
568
|
+
}
|
|
569
|
+
return this._sendGroupMessage(toJid, msgId, plaintext, mediaType,
|
|
570
|
+
Object.assign({}, options, { _noSessionRetry: true }));
|
|
571
|
+
}
|
|
557
572
|
throw err;
|
|
558
573
|
}
|
|
559
574
|
}
|
|
@@ -587,11 +602,41 @@ class MessageSender {
|
|
|
587
602
|
return this._sendDMMessage(toJid, retryMsgId, plaintext, mediaType,
|
|
588
603
|
Object.assign({}, options, { _479retry: true }));
|
|
589
604
|
}
|
|
605
|
+
// ── No session at all ────────────────────────────────────────────────────
|
|
606
|
+
// Nothing could be encrypted for any device, so there is no ciphertext to
|
|
607
|
+
// send. This is what a timed-out prekey IQ leaves behind: the device list
|
|
608
|
+
// is cached but no session sits behind it, and every later send fails the
|
|
609
|
+
// same way because the cache hides the gap. Purge both and retry once, so
|
|
610
|
+
// the next attempt re-fetches bundles and opens fresh pkmsg sessions.
|
|
611
|
+
if (err && /NO_OPEN_SESSION/.test(err.message) && !options._noSessionRetry) {
|
|
612
|
+
_whaDbg('[DBG] NO_OPEN_SESSION dm ' + toJid + ' — forcing fresh pkmsg\n');
|
|
613
|
+
await this._forceSessionRebuild([toJid]);
|
|
614
|
+
return this._sendDMMessage(toJid, generateMessageId(), plaintext, mediaType,
|
|
615
|
+
Object.assign({}, options, { _noSessionRetry: true }));
|
|
616
|
+
}
|
|
590
617
|
// ─────────────────────────────────────────────────────────────────────────
|
|
591
618
|
throw err;
|
|
592
619
|
}
|
|
593
620
|
}
|
|
594
621
|
|
|
622
|
+
// Drop every cached device list and Signal session for these JIDs so the next
|
|
623
|
+
// send is forced to re-query devices, re-fetch prekey bundles and open new
|
|
624
|
+
// pkmsg sessions from scratch.
|
|
625
|
+
async _forceSessionRebuild(jids) {
|
|
626
|
+
const phones = [...new Set((jids || []).map(phoneFromJid).filter(Boolean))];
|
|
627
|
+
for (const phone of phones) {
|
|
628
|
+
await this._clearSignalSessionsForRecipient(phone).catch(() => {});
|
|
629
|
+
}
|
|
630
|
+
if (this._devMgr && phones.length) {
|
|
631
|
+
this._devMgr.clearCache(phones);
|
|
632
|
+
// LID device lists are cached under a separate key.
|
|
633
|
+
if (typeof this._devMgr._dcDel === 'function') {
|
|
634
|
+
this._devMgr._dcDel(phones.map(p => 'lid:' + p));
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
_whaDbg('[DBG] FORCE_SESSION_REBUILD purged ' + phones.join(', ') + '\n');
|
|
638
|
+
}
|
|
639
|
+
|
|
595
640
|
// ─── Clear all Signal sessions for a phone-number recipient ─────────────────
|
|
596
641
|
//
|
|
597
642
|
// Called on error 479 (stale session). Deletes:
|
|
@@ -320,6 +320,14 @@ function jidToAddress(jid) {
|
|
|
320
320
|
} else if (jid && jid.user) {
|
|
321
321
|
user = jid.user;
|
|
322
322
|
deviceId = jid.device || 0;
|
|
323
|
+
// A @lid JID is encoded as JID_PAIR with the device kept inside the user
|
|
324
|
+
// part ("112713111982325:49"), so an object can carry its device there
|
|
325
|
+
// rather than in .device. Without this the address came out as device 0.
|
|
326
|
+
const colonIdx = String(user).indexOf(':');
|
|
327
|
+
if (colonIdx >= 0) {
|
|
328
|
+
if (!deviceId) deviceId = parseInt(String(user).slice(colonIdx + 1), 10) || 0;
|
|
329
|
+
user = String(user).slice(0, colonIdx);
|
|
330
|
+
}
|
|
323
331
|
} else {
|
|
324
332
|
user = String(jid);
|
|
325
333
|
}
|