whalibmob 5.1.21 → 5.5.22
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/README.md +76 -83
- package/cli.js +2 -2
- package/index.js +4 -2
- package/lib/Client.js +19 -0
- package/lib/DeviceConfig.js +51 -36
- package/lib/DeviceManager.js +414 -33
- package/lib/MediaService.js +1 -1
- package/lib/Registration.js +102 -97
- package/lib/Store.js +2 -2
- package/lib/constants.js +116 -101
- package/lib/messages/MessageSender.js +51 -5
- package/lib/noise.js +5 -8
- package/lib/signal/SignalStore.js +20 -2
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ const crypto = require('crypto');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const { BinaryNode } = require('../BinaryNode');
|
|
7
|
-
const { DeviceManager } = require('../DeviceManager');
|
|
7
|
+
const { DeviceManager, jidStrToObj } = require('../DeviceManager');
|
|
8
8
|
const {
|
|
9
9
|
encodeMessage,
|
|
10
10
|
encodeText, encodeImageMessage, encodeVideoMessage,
|
|
@@ -553,12 +553,42 @@ class MessageSender {
|
|
|
553
553
|
// Primary devices can always use pkmsg to establish new Signal sessions —
|
|
554
554
|
// they simply omit the device-identity node (server accepts this for primaries).
|
|
555
555
|
const allowPkmsg = true;
|
|
556
|
-
|
|
556
|
+
|
|
557
|
+
// First, run the phone-based usync (and own devices) in parallel.
|
|
558
|
+
// IMPORTANT: _pnToLid may be empty RIGHT NOW but gets populated during this
|
|
559
|
+
// await — incoming messages from the recipient (which arrive while we wait
|
|
560
|
+
// for the usync timeout) tell us their LID JID via from=LID + sender_pn attrs.
|
|
561
|
+
// We therefore check _pnToLid AFTER this await, not before.
|
|
562
|
+
const [_recipientDevicesByPhone, ownDevices] = await Promise.all([
|
|
557
563
|
this._devMgr.bulkEnsureSessions([recipientPhone], this._signal, allowPkmsg),
|
|
558
564
|
this._devMgr.ensureOwnDeviceSessions(ownPhone, this._signal, allowPkmsg)
|
|
559
565
|
]);
|
|
560
566
|
|
|
561
|
-
|
|
567
|
+
// ── LID routing fix ───────────────────────────────────────────────────────
|
|
568
|
+
// Check _pnToLid NOW — after the await above, incoming messages from the
|
|
569
|
+
// recipient during the usync wait will have populated this map.
|
|
570
|
+
// For LID-migrated accounts, the message `to` field AND Signal participants
|
|
571
|
+
// must use the LID JID. Phone-JID messages are silently accepted by the
|
|
572
|
+
// server but never delivered to the recipient's LID-registered device.
|
|
573
|
+
const lidUser = this._client._pnToLid && this._client._pnToLid.get(recipientPhone);
|
|
574
|
+
const routingToJid = lidUser ? `${lidUser}@lid` : toJid;
|
|
575
|
+
|
|
576
|
+
process.stderr.write('[DBG] DM_ROUTE phone=' + recipientPhone +
|
|
577
|
+
' routing=' + routingToJid + (lidUser ? ' (LID)' : ' (PN)') + '\n');
|
|
578
|
+
|
|
579
|
+
let otherJids;
|
|
580
|
+
if (lidUser) {
|
|
581
|
+
// Fetch bundle + build Signal session for the LID JID.
|
|
582
|
+
// skipUsync=true: we already know the LID from _pnToLid — skip a second
|
|
583
|
+
// 15 s usync round-trip and go straight to bundle fetch (device 0 primary).
|
|
584
|
+
const lidDevices = await this._devMgr.bulkEnsureSessionsForLid(
|
|
585
|
+
lidUser, this._signal, allowPkmsg, /* skipUsync */ true);
|
|
586
|
+
otherJids = lidDevices.length > 0 ? lidDevices : [routingToJid];
|
|
587
|
+
} else {
|
|
588
|
+
otherJids = _recipientDevicesByPhone.length > 0 ? _recipientDevicesByPhone : [toJid];
|
|
589
|
+
}
|
|
590
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
591
|
+
|
|
562
592
|
const ownLinkedJids = ownDevices.filter(j => j !== ownMainJid);
|
|
563
593
|
|
|
564
594
|
const allParticipants = [...otherJids, ...ownLinkedJids];
|
|
@@ -577,7 +607,10 @@ class MessageSender {
|
|
|
577
607
|
|
|
578
608
|
const encryptedList = [...otherEncrypted, ...ownEncrypted];
|
|
579
609
|
|
|
580
|
-
|
|
610
|
+
// Encode the routing JID as a binary JID object (JID_PAIR for @lid / @s.whatsapp.net).
|
|
611
|
+
// Raw UTF-8 strings are not recognised by the server for LID recipients, causing
|
|
612
|
+
// the message to be accepted (server ACK) but never delivered to the device.
|
|
613
|
+
const stanzaAttrs = { to: jidStrToObj(routingToJid), id: msgId, type: mediaType, t: String(msNow()) };
|
|
581
614
|
if (phash) stanzaAttrs.phash = phash;
|
|
582
615
|
if (options.edit) stanzaAttrs.edit = String(options.edit);
|
|
583
616
|
|
|
@@ -599,6 +632,19 @@ class MessageSender {
|
|
|
599
632
|
|
|
600
633
|
const msgNode = new BinaryNode('message', stanzaAttrs, msgContent);
|
|
601
634
|
|
|
635
|
+
// Debug: log outgoing stanza details
|
|
636
|
+
process.stderr.write('[DBG] DM_SEND to=' + toJid +
|
|
637
|
+
' otherJids=[' + otherJids.join(',') + ']' +
|
|
638
|
+
' ownLinked=[' + ownLinkedJids.join(',') + ']' +
|
|
639
|
+
' encrypted=' + encryptedList.length +
|
|
640
|
+
' hasPkmsg=' + hasPkmsg +
|
|
641
|
+
' hasAdv=' + !!(advBytes) +
|
|
642
|
+
'\n');
|
|
643
|
+
if (encryptedList.length > 0) {
|
|
644
|
+
process.stderr.write('[DBG] DM_PARTICIPANTS ' +
|
|
645
|
+
encryptedList.map(e => e.jid + '(' + e.type + ')').join(', ') + '\n');
|
|
646
|
+
}
|
|
647
|
+
|
|
602
648
|
// Cache plaintext so Client can re-send with fresh session on recipient retry
|
|
603
649
|
if (this._client._sentMsgCache) {
|
|
604
650
|
this._client._sentMsgCache.set(msgId, {
|
|
@@ -699,7 +745,7 @@ class MessageSender {
|
|
|
699
745
|
msgContent.push(new BinaryNode('enc', skmsgAttrs, Buffer.isBuffer(skmsgCiphertext) ? skmsgCiphertext : Buffer.from(skmsgCiphertext)));
|
|
700
746
|
|
|
701
747
|
const stanzaAttrs = {
|
|
702
|
-
to: groupJid,
|
|
748
|
+
to: jidStrToObj(groupJid),
|
|
703
749
|
id: msgId,
|
|
704
750
|
type: mediaType,
|
|
705
751
|
addressing_mode: groupAddressingMode,
|
package/lib/noise.js
CHANGED
|
@@ -9,7 +9,6 @@ const { hkdf } = require('@noble/hashes/hkdf');
|
|
|
9
9
|
const { MOBILE_PROLOGUE, WHATSAPP_HOST, WHATSAPP_PORT } = require('./constants');
|
|
10
10
|
const { encodeHandshakeClientHello, encodeHandshakeClientFinish,
|
|
11
11
|
decodeServerHello, encodeClientPayload } = require('./proto');
|
|
12
|
-
const { parsePhone, getCountryMeta } = require('./Registration');
|
|
13
12
|
|
|
14
13
|
// ─── CertChain validator ────────
|
|
15
14
|
//
|
|
@@ -283,8 +282,6 @@ class NoiseSocket extends EventEmitter {
|
|
|
283
282
|
const sharedSS = dhShared(noisePriv, serverHello.ephemeral);
|
|
284
283
|
this.noiseState.mixKey(sharedSS);
|
|
285
284
|
|
|
286
|
-
const { cc: _cc } = parsePhone(this.store.phoneNumber);
|
|
287
|
-
const _meta = getCountryMeta(_cc);
|
|
288
285
|
const payload = encodeClientPayload({
|
|
289
286
|
username: BigInt(this.store.phoneNumber),
|
|
290
287
|
passive: false,
|
|
@@ -296,18 +293,18 @@ class NoiseSocket extends EventEmitter {
|
|
|
296
293
|
device: 0,
|
|
297
294
|
oc: false,
|
|
298
295
|
userAgent: {
|
|
299
|
-
platform: 1,
|
|
296
|
+
platform: (this.store.device && this.store.device.platform) || 1,
|
|
300
297
|
version: this.store.version,
|
|
301
|
-
mcc:
|
|
302
|
-
mnc:
|
|
298
|
+
mcc: '000',
|
|
299
|
+
mnc: '000',
|
|
303
300
|
osVersion: this.store.device.osVersion,
|
|
304
301
|
manufacturer: this.store.device.manufacturer,
|
|
305
302
|
device: this.store.device.model,
|
|
306
303
|
osBuildNumber: this.store.device.osBuildNumber,
|
|
307
304
|
phoneId: this.store.fdid.toUpperCase(),
|
|
308
305
|
releaseChannel: 0,
|
|
309
|
-
localeLanguage:
|
|
310
|
-
localeCountry:
|
|
306
|
+
localeLanguage: 'en',
|
|
307
|
+
localeCountry: 'US',
|
|
311
308
|
deviceType: 0,
|
|
312
309
|
deviceModelType: this.store.device.modelId
|
|
313
310
|
}
|
|
@@ -26,6 +26,7 @@ class SignalStore {
|
|
|
26
26
|
this._signedPreKeys = {};
|
|
27
27
|
this._identities = {};
|
|
28
28
|
this._filePath = null;
|
|
29
|
+
this._lidMappings = {}; // phone → lid — persisted alongside sessions
|
|
29
30
|
|
|
30
31
|
// Debounce state
|
|
31
32
|
this._dirty = false;
|
|
@@ -56,9 +57,24 @@ class SignalStore {
|
|
|
56
57
|
this._preKeys = raw.preKeys || {};
|
|
57
58
|
this._signedPreKeys = raw.signedPreKeys || {};
|
|
58
59
|
this._identities = raw.identities || {};
|
|
60
|
+
this._lidMappings = raw.lidMappings || {}; // phone → lid, persisted
|
|
59
61
|
} catch (_) {}
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
// Persist a phone ↔ LID mapping. Called any time we learn a new mapping from
|
|
65
|
+
// incoming messages or from a contact usync fallback query.
|
|
66
|
+
setLidMapping(phone, lid) {
|
|
67
|
+
if (!phone || !lid) return;
|
|
68
|
+
if (this._lidMappings[phone] === lid) return; // already stored, skip disk write
|
|
69
|
+
this._lidMappings[phone] = lid;
|
|
70
|
+
this._save();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Return all stored phone → lid mappings (used to populate Client._pnToLid on startup)
|
|
74
|
+
getLidMappings() {
|
|
75
|
+
return this._lidMappings;
|
|
76
|
+
}
|
|
77
|
+
|
|
62
78
|
// Immediate synchronous flush (used on process exit only)
|
|
63
79
|
_flushSync() {
|
|
64
80
|
if (!this._dirty || !this._filePath) return;
|
|
@@ -68,7 +84,8 @@ class SignalStore {
|
|
|
68
84
|
sessions: this._sessions,
|
|
69
85
|
preKeys: this._preKeys,
|
|
70
86
|
signedPreKeys: this._signedPreKeys,
|
|
71
|
-
identities: this._identities
|
|
87
|
+
identities: this._identities,
|
|
88
|
+
lidMappings: this._lidMappings
|
|
72
89
|
});
|
|
73
90
|
const dir = path.dirname(this._filePath);
|
|
74
91
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
@@ -89,7 +106,8 @@ class SignalStore {
|
|
|
89
106
|
sessions: this._sessions,
|
|
90
107
|
preKeys: this._preKeys,
|
|
91
108
|
signedPreKeys: this._signedPreKeys,
|
|
92
|
-
identities: this._identities
|
|
109
|
+
identities: this._identities,
|
|
110
|
+
lidMappings: this._lidMappings
|
|
93
111
|
});
|
|
94
112
|
const dir = path.dirname(this._filePath);
|
|
95
113
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|