whalibmob 5.6.2 → 5.6.4
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 +24 -0
- package/cli.js +19 -2
- package/index.js +2 -0
- package/lib/Client.js +73 -2
- package/lib/DeviceManager.js +26 -3
- package/lib/WebVersion.js +78 -0
- package/lib/constants.js +8 -4
- package/lib/messages/MessageSender.js +12 -3
- package/lib/webproto.js +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -669,6 +669,29 @@ Run it once, type the code into the phone, and it is linked. Run it again and it
|
|
|
669
669
|
|---|---|---|
|
|
670
670
|
| `syncFullHistory` | `true` | Ask the phone for the full archive. `false` requests recent messages only and links noticeably faster. |
|
|
671
671
|
| `browser` | `['Ubuntu', 'Chrome', '120.0.0.0']` | `[os, client, version]`. The second element picks the icon shown under Linked Devices: `Chrome`, `Firefox`, `Safari`, `Edge`, `Opera`, `Desktop`. |
|
|
672
|
+
| `version` | fetched live | Web client revision to announce, e.g. `[2, 3000, 1035194821]`. Set it to pin one. |
|
|
673
|
+
| `fetchVersion` | `true` | Set `false` to skip the live lookup and use the pinned fallback. |
|
|
674
|
+
|
|
675
|
+
#### The Announced Version
|
|
676
|
+
|
|
677
|
+
The web endpoint checks the client revision during the handshake and refuses an unrecognised one with `<failure reason="405">` — before any stanza is exchanged, and with nothing in the failure to say the version was the problem. The mobile endpoint is far more forgiving; this one is not.
|
|
678
|
+
|
|
679
|
+
whalibmob therefore reads the live revision from WhatsApp Web's own service worker on each `connectWeb()`, and falls back to a pinned value if that lookup fails. You should not have to think about it, but you can:
|
|
680
|
+
|
|
681
|
+
```js
|
|
682
|
+
const { fetchWaWebVersion } = require('whalibmob')
|
|
683
|
+
|
|
684
|
+
const { version, isLatest } = await fetchWaWebVersion()
|
|
685
|
+
console.log(version, isLatest) // [2, 3000, 1035194821] true
|
|
686
|
+
|
|
687
|
+
// pin it yourself
|
|
688
|
+
await client.connectWeb(phone, { version: [2, 3000, 1035194821] })
|
|
689
|
+
|
|
690
|
+
// or skip the lookup entirely
|
|
691
|
+
await client.connectWeb(phone, { fetchVersion: false })
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
If you ever see `405`, this is what it means. It is not a revoked session and there is nothing to re-pair — reconnect to pick up the current revision.
|
|
672
695
|
|
|
673
696
|
`requestPairingCode(phone, customCode)`:
|
|
674
697
|
|
|
@@ -687,6 +710,7 @@ Every event from the SMS primary API fires here too — `message`, `receipt`, `p
|
|
|
687
710
|
| `pair_device` | `{ refs }` | the QR path produced reference strings |
|
|
688
711
|
| `history_sync` | `{ syncTypeName, chats, contacts, pushNames, merged }` | a chunk of history arrived |
|
|
689
712
|
| `history_sync_error` | `{ err, notification }` | a chunk could not be fetched or decrypted |
|
|
713
|
+
| `client_rejected` | `{ reason, location, message }` | the server refused the client itself, not the session — `405` means the announced version is not accepted. Distinct from `auth_failure`, and there is nothing to re-pair. |
|
|
690
714
|
|
|
691
715
|
### Reading What the Phone Sent
|
|
692
716
|
|
package/cli.js
CHANGED
|
@@ -572,7 +572,19 @@ function attachEvents(client) {
|
|
|
572
572
|
|
|
573
573
|
client.on('auth_failure', (f) => {
|
|
574
574
|
_rl && _rl.pause();
|
|
575
|
-
|
|
575
|
+
const how = client._mode === 'web'
|
|
576
|
+
? 'link again with /pair'
|
|
577
|
+
: 're-register with /reg code';
|
|
578
|
+
fail('session revoked: ' + f.reason + ' — ' + how);
|
|
579
|
+
_rl && (_rl.resume(), _rl.prompt(true));
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// The server declined the client, not the session. Nothing to re-register.
|
|
583
|
+
client.on('client_rejected', (r) => {
|
|
584
|
+
_rl && _rl.pause();
|
|
585
|
+
fail('rejected by WhatsApp (' + r.reason +
|
|
586
|
+
(r.location ? ', edge ' + r.location : '') + ')');
|
|
587
|
+
out(' ' + r.message);
|
|
576
588
|
_rl && (_rl.resume(), _rl.prompt(true));
|
|
577
589
|
});
|
|
578
590
|
|
|
@@ -812,7 +824,12 @@ async function handleLine(line) {
|
|
|
812
824
|
|
|
813
825
|
case '/pair': {
|
|
814
826
|
const ph = p[1] || _phone;
|
|
815
|
-
if (!ph) {
|
|
827
|
+
if (!ph) {
|
|
828
|
+
fail('usage: /pair <phone> [8-char-code]');
|
|
829
|
+
out(' the phone number is the account you want to link to, digits only');
|
|
830
|
+
out(' example: /pair 40756469325');
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
816
833
|
if (_client && _client.connected) { fail('already connected — /disconnect first'); break; }
|
|
817
834
|
out('connecting...');
|
|
818
835
|
await doConnectWeb(ph, { customCode: p[2] });
|
package/index.js
CHANGED
|
@@ -28,6 +28,7 @@ const {
|
|
|
28
28
|
buildCompanionFinishBundle
|
|
29
29
|
} = require('./lib/PairingCode');
|
|
30
30
|
const { configureSuccessfulPairing } = require('./lib/CompanionPairing');
|
|
31
|
+
const { fetchWaWebVersion } = require('./lib/WebVersion');
|
|
31
32
|
const {
|
|
32
33
|
encodeCompanionRegisterPayload,
|
|
33
34
|
encodeCompanionLoginPayload
|
|
@@ -81,6 +82,7 @@ module.exports = {
|
|
|
81
82
|
configureSuccessfulPairing,
|
|
82
83
|
encodeCompanionRegisterPayload,
|
|
83
84
|
encodeCompanionLoginPayload,
|
|
85
|
+
fetchWaWebVersion,
|
|
84
86
|
// Signal / encryption internals
|
|
85
87
|
SignalProtocol,
|
|
86
88
|
SignalStore,
|
package/lib/Client.js
CHANGED
|
@@ -198,6 +198,10 @@ class WhalibmobClient extends EventEmitter {
|
|
|
198
198
|
// account as a companion, via pairing code.
|
|
199
199
|
this._mode = 'mobile';
|
|
200
200
|
this._webSessionFile = null;
|
|
201
|
+
// Set once the server has told us to stop trying: an account logout, or a
|
|
202
|
+
// client the server will not accept. Suppresses the reconnect loop.
|
|
203
|
+
this._fatal = false;
|
|
204
|
+
this._closing = false;
|
|
201
205
|
this._pingTimer = null;
|
|
202
206
|
this._keepTimer = null;
|
|
203
207
|
this._connected = false;
|
|
@@ -267,6 +271,9 @@ class WhalibmobClient extends EventEmitter {
|
|
|
267
271
|
async init(phoneNumber) {
|
|
268
272
|
phoneNumber = String(phoneNumber).replace(/\D/g, '');
|
|
269
273
|
this._phoneNumber = phoneNumber;
|
|
274
|
+
// A fresh attempt clears whatever made the last one fatal. Only the public
|
|
275
|
+
// entry points reset it — the reconnect loop must not clear its own guard.
|
|
276
|
+
this._fatal = false;
|
|
270
277
|
|
|
271
278
|
const sessionFile = path.join(this._sessionDir, `${phoneNumber}.json`);
|
|
272
279
|
this._store = loadStore(sessionFile);
|
|
@@ -354,6 +361,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
354
361
|
phoneNumber = String(phoneNumber).replace(/\D/g, '');
|
|
355
362
|
this._phoneNumber = phoneNumber;
|
|
356
363
|
this._mode = 'web';
|
|
364
|
+
this._fatal = false;
|
|
357
365
|
|
|
358
366
|
const { createNewWebStore, loadWebStore, saveWebStore, webSessionPath } =
|
|
359
367
|
require('./WebStore');
|
|
@@ -363,6 +371,25 @@ class WhalibmobClient extends EventEmitter {
|
|
|
363
371
|
|
|
364
372
|
if (opts.syncFullHistory !== undefined) this._store.syncFullHistory = !!opts.syncFullHistory;
|
|
365
373
|
if (opts.browser) this._store.browser = opts.browser;
|
|
374
|
+
|
|
375
|
+
// Announce the revision the web client is actually on.
|
|
376
|
+
//
|
|
377
|
+
// The web endpoint rejects an unrecognised version during the handshake
|
|
378
|
+
// with <failure reason="405"> and says nothing about why, so a version
|
|
379
|
+
// pinned in source is a connection that stops working on WhatsApp's
|
|
380
|
+
// schedule rather than ours. Read the live one, keep the pinned value as
|
|
381
|
+
// the fallback, and let a caller override both.
|
|
382
|
+
if (opts.version) {
|
|
383
|
+
this._store.webVersion = opts.version;
|
|
384
|
+
} else if (opts.fetchVersion !== false) {
|
|
385
|
+
const { fetchWaWebVersion } = require('./WebVersion');
|
|
386
|
+
const res = await fetchWaWebVersion();
|
|
387
|
+
this._store.webVersion = res.version;
|
|
388
|
+
_whaDbg('[DBG] WEB_VERSION ' + res.version.join('.') +
|
|
389
|
+
(res.isLatest ? ' (live)' : ' (pinned fallback: ' +
|
|
390
|
+
(res.error && res.error.message) + ')'));
|
|
391
|
+
}
|
|
392
|
+
|
|
366
393
|
saveWebStore(this._store, this._webSessionFile);
|
|
367
394
|
|
|
368
395
|
// Signal state lives in its own files so a web link and an SMS
|
|
@@ -721,6 +748,12 @@ class WhalibmobClient extends EventEmitter {
|
|
|
721
748
|
}
|
|
722
749
|
|
|
723
750
|
disconnect() {
|
|
751
|
+
// Closing the socket makes it emit 'close', which lands in _onClose and
|
|
752
|
+
// schedules a reconnect. That is right for a dropped connection and wrong
|
|
753
|
+
// for a deliberate teardown — and it is what turned a single <failure> into
|
|
754
|
+
// an endless retry loop, since clearing _reconnecting was not enough to
|
|
755
|
+
// stop the handler that sets it again.
|
|
756
|
+
this._closing = true;
|
|
724
757
|
this._reconnecting = false;
|
|
725
758
|
this._reconnectTry = 0;
|
|
726
759
|
this._stopTimers();
|
|
@@ -730,11 +763,16 @@ class WhalibmobClient extends EventEmitter {
|
|
|
730
763
|
}
|
|
731
764
|
this._connected = false;
|
|
732
765
|
this.emit('close');
|
|
766
|
+
this._closing = false;
|
|
733
767
|
}
|
|
734
768
|
|
|
735
769
|
// ─── Reconnection ─────────────────────────────────────────────────────────
|
|
736
770
|
|
|
737
771
|
_scheduleReconnect() {
|
|
772
|
+
// A fatal failure — the account logged us out, or the server refused the
|
|
773
|
+
// client outright — will fail identically every time. Retrying it only
|
|
774
|
+
// fills the terminal.
|
|
775
|
+
if (this._closing || this._fatal) return;
|
|
738
776
|
if (this._reconnecting) return;
|
|
739
777
|
this._reconnecting = true;
|
|
740
778
|
const delay = RECONNECT_BACKOFF[Math.min(this._reconnectTry, RECONNECT_BACKOFF.length - 1)];
|
|
@@ -2068,11 +2106,44 @@ class WhalibmobClient extends EventEmitter {
|
|
|
2068
2106
|
|
|
2069
2107
|
// ─── Auth failure (server forces logout during active session) ──────────────
|
|
2070
2108
|
|
|
2109
|
+
// <failure> ends the connection, but not every reason means the session died.
|
|
2110
|
+
//
|
|
2111
|
+
// 401, 403 and 419 are the ones that do: the account logged us out, or the
|
|
2112
|
+
// owner removed the device. Those are unrecoverable and reconnecting only
|
|
2113
|
+
// burns attempts.
|
|
2114
|
+
//
|
|
2115
|
+
// 405 is different, and reading it as "session revoked" sent people looking
|
|
2116
|
+
// in the wrong place. It means the server declined the client itself — on the
|
|
2117
|
+
// web endpoint, almost always because the announced version is one it does
|
|
2118
|
+
// not recognise. Nothing about the session is wrong; retrying with the same
|
|
2119
|
+
// version will fail identically, so say what it is instead.
|
|
2071
2120
|
_handleFailure(node) {
|
|
2072
2121
|
const attrs = (node && node.attrs) || {};
|
|
2073
|
-
const reason = attrs.reason || attrs.location || 'unknown';
|
|
2074
|
-
|
|
2122
|
+
const reason = String(attrs.reason || attrs.location || 'unknown');
|
|
2123
|
+
const loc = attrs.location ? String(attrs.location) : null;
|
|
2124
|
+
|
|
2125
|
+
if (reason === '405') {
|
|
2126
|
+
const detail = this._mode === 'web'
|
|
2127
|
+
? 'WhatsApp refused this client (405). The web version we announced (' +
|
|
2128
|
+
((this._store && this._store.webVersion) || []).join('.') +
|
|
2129
|
+
') is not one the server accepts — it changes on their schedule. ' +
|
|
2130
|
+
'Reconnect to pick up the current one, or pass { version: [2, 3000, N] } ' +
|
|
2131
|
+
'to connectWeb().'
|
|
2132
|
+
: 'WhatsApp refused this client (405) — the announced app version is ' +
|
|
2133
|
+
'not accepted. Update the version in the session store.';
|
|
2134
|
+
_whaDbg('[DBG] FAILURE 405 location=' + loc + ' — client refused, not a dead session');
|
|
2135
|
+
this._fatal = true;
|
|
2136
|
+
this._reconnecting = false;
|
|
2137
|
+
this.emit('client_rejected', { reason, location: loc, message: detail, node });
|
|
2138
|
+
this.emit('error', new Error(detail));
|
|
2139
|
+
this.disconnect();
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
_whaDbg('[DBG] FAILURE reason=' + reason + ' location=' + loc);
|
|
2144
|
+
this.emit('auth_failure', { reason, location: loc, node });
|
|
2075
2145
|
// The session is no longer valid — stop reconnecting and disconnect
|
|
2146
|
+
this._fatal = true;
|
|
2076
2147
|
this._reconnecting = false;
|
|
2077
2148
|
this.disconnect();
|
|
2078
2149
|
}
|
package/lib/DeviceManager.js
CHANGED
|
@@ -901,7 +901,10 @@ class DeviceManager {
|
|
|
901
901
|
}
|
|
902
902
|
}
|
|
903
903
|
|
|
904
|
-
|
|
904
|
+
// Same rule as the phone-addressed path: skip the device we are, not the
|
|
905
|
+
// device that happens to be numbered zero.
|
|
906
|
+
const selfDevice = this._ownDeviceIndex();
|
|
907
|
+
const deviceIds = (this._dcGet(cacheKey) || []).filter(d => d !== selfDevice);
|
|
905
908
|
const deviceJids = deviceIds.map(d => makeDeviceJid(lidUser, d, 'lid'));
|
|
906
909
|
if (deviceJids.length === 0) return [];
|
|
907
910
|
|
|
@@ -1085,10 +1088,30 @@ class DeviceManager {
|
|
|
1085
1088
|
// On the first call we query usync, on all subsequent calls we return
|
|
1086
1089
|
// from cache — zero extra network round-trips.
|
|
1087
1090
|
//
|
|
1091
|
+
// Which device index this client occupies.
|
|
1092
|
+
//
|
|
1093
|
+
// A device registered over SMS is the account's primary and is always 0. A
|
|
1094
|
+
// companion linked by pairing code occupies a numbered slot — and then
|
|
1095
|
+
// device 0 is the owner's phone, which is a recipient like any other.
|
|
1096
|
+
_ownDeviceIndex() {
|
|
1097
|
+
const store = this._client && this._client._store;
|
|
1098
|
+
return (store && store.deviceIndex) || 0;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1088
1101
|
async ensureOwnDeviceSessions(ownPhone, signalProto, allowPkmsg = true) {
|
|
1102
|
+
// Everything the account owns except the device doing the sending.
|
|
1103
|
+
//
|
|
1104
|
+
// This used to drop device 0 unconditionally, which is right only while we
|
|
1105
|
+
// are device 0. As a companion it silently excluded the owner's own phone
|
|
1106
|
+
// from every message we sent: the recipient got it, the phone did not, and
|
|
1107
|
+
// it sat there showing "waiting for this message" forever. It also put our
|
|
1108
|
+
// participant list out of step with the server's, which is what the phash
|
|
1109
|
+
// mismatch on the ack was reporting.
|
|
1110
|
+
const selfDevice = this._ownDeviceIndex();
|
|
1111
|
+
|
|
1089
1112
|
// Use cached own device list if it hasn't expired (5-min TTL matches _deviceCache)
|
|
1090
1113
|
if (this._ownDeviceJids !== null && Date.now() < this._ownDeviceExpiry) {
|
|
1091
|
-
const others = this._ownDeviceJids.filter(j => stripUser(j).device !==
|
|
1114
|
+
const others = this._ownDeviceJids.filter(j => stripUser(j).device !== selfDevice);
|
|
1092
1115
|
return this._ensureSessions(others, signalProto, allowPkmsg);
|
|
1093
1116
|
}
|
|
1094
1117
|
|
|
@@ -1097,7 +1120,7 @@ class DeviceManager {
|
|
|
1097
1120
|
this._ownDeviceJids = deviceJids;
|
|
1098
1121
|
this._ownDeviceExpiry = Date.now() + 5 * 60 * 1000;
|
|
1099
1122
|
|
|
1100
|
-
const others = deviceJids.filter(j => stripUser(j).device !==
|
|
1123
|
+
const others = deviceJids.filter(j => stripUser(j).device !== selfDevice);
|
|
1101
1124
|
return this._ensureSessions(others, signalProto, allowPkmsg);
|
|
1102
1125
|
}
|
|
1103
1126
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The web client's own revision number.
|
|
4
|
+
//
|
|
5
|
+
// The mobile endpoint tolerates a version that has drifted by months. The web
|
|
6
|
+
// endpoint does not: it checks the revision during the Noise handshake and
|
|
7
|
+
// refuses an unrecognised one with <failure reason="405"> before a single
|
|
8
|
+
// stanza has been exchanged. There is nothing in that failure to say the
|
|
9
|
+
// version was the problem, which makes a stale constant an expensive thing to
|
|
10
|
+
// carry.
|
|
11
|
+
//
|
|
12
|
+
// So read the live one. WhatsApp Web ships its service worker at
|
|
13
|
+
// /sw.js with the current revision embedded in it; that number, paired with
|
|
14
|
+
// the fixed 2.3000 prefix, is the version the browser announces.
|
|
15
|
+
//
|
|
16
|
+
// Everything here is best-effort — a failed fetch falls back to the pinned
|
|
17
|
+
// value rather than blocking a connection.
|
|
18
|
+
|
|
19
|
+
const https = require('https');
|
|
20
|
+
const { WEB_VERSION_FALLBACK } = require('./constants');
|
|
21
|
+
|
|
22
|
+
const SW_URL = 'https://web.whatsapp.com/sw.js';
|
|
23
|
+
const CACHE_MS = 60 * 60 * 1000; // an hour; the revision changes far slower
|
|
24
|
+
|
|
25
|
+
let _cached = null;
|
|
26
|
+
let _cachedAt = 0;
|
|
27
|
+
|
|
28
|
+
function fetchText(url, timeoutMs) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const req = https.get(url, {
|
|
31
|
+
headers: {
|
|
32
|
+
// The minimum that gets past the anti-bot check on this path.
|
|
33
|
+
'sec-fetch-site': 'none',
|
|
34
|
+
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' +
|
|
35
|
+
'(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
|
|
36
|
+
}
|
|
37
|
+
}, (res) => {
|
|
38
|
+
if (res.statusCode !== 200) {
|
|
39
|
+
res.resume();
|
|
40
|
+
return reject(new Error('sw.js returned HTTP ' + res.statusCode));
|
|
41
|
+
}
|
|
42
|
+
const chunks = [];
|
|
43
|
+
res.on('data', c => chunks.push(c));
|
|
44
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
45
|
+
res.on('error', reject);
|
|
46
|
+
});
|
|
47
|
+
req.on('error', reject);
|
|
48
|
+
req.setTimeout(timeoutMs || 10000, () => req.destroy(new Error('sw.js fetch timed out')));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the version to announce.
|
|
54
|
+
*
|
|
55
|
+
* @param {object} [opts] { force } to bypass the hourly cache
|
|
56
|
+
* @returns {Promise<{version: number[], isLatest: boolean, error?: Error}>}
|
|
57
|
+
*/
|
|
58
|
+
async function fetchWaWebVersion(opts) {
|
|
59
|
+
opts = opts || {};
|
|
60
|
+
if (!opts.force && _cached && (Date.now() - _cachedAt) < CACHE_MS) {
|
|
61
|
+
return { version: _cached.slice(), isLatest: true };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const body = await fetchText(SW_URL, opts.timeoutMs);
|
|
66
|
+
const match = body.match(/\\?"client_revision\\?":\s*(\d+)/);
|
|
67
|
+
if (!match || !match[1]) throw new Error('client_revision not found in sw.js');
|
|
68
|
+
|
|
69
|
+
const version = [2, 3000, parseInt(match[1], 10)];
|
|
70
|
+
_cached = version;
|
|
71
|
+
_cachedAt = Date.now();
|
|
72
|
+
return { version: version.slice(), isLatest: true };
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return { version: WEB_VERSION_FALLBACK.slice(), isLatest: false, error };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { fetchWaWebVersion, WEB_VERSION_FALLBACK };
|
package/lib/constants.js
CHANGED
|
@@ -18,10 +18,14 @@ const WEB_PROLOGUE = Buffer.from([0x57, 0x41, 0x06, 0x03]);
|
|
|
18
18
|
const WEB_SOCKET_URL = 'wss://web.whatsapp.com/ws/chat';
|
|
19
19
|
const WEB_ORIGIN = 'https://web.whatsapp.com';
|
|
20
20
|
|
|
21
|
-
// Version the companion announces.
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
21
|
+
// Version the companion announces.
|
|
22
|
+
//
|
|
23
|
+
// Not an App Store build like the mobile version — this is the web client's
|
|
24
|
+
// own revision, and the server is far less tolerant of it than the mobile
|
|
25
|
+
// endpoint is: an unrecognised revision is refused during the handshake with
|
|
26
|
+
// <failure reason="405">, before any stanza is exchanged. Keep it current, or
|
|
27
|
+
// let fetchWaWebVersion() read the live one.
|
|
28
|
+
const WEB_VERSION_FALLBACK = [2, 3000, 1035194821];
|
|
25
29
|
|
|
26
30
|
// [os, browser] pair. Decides the DeviceProps.platformType we register with and
|
|
27
31
|
// the name the account owner sees in the linked-devices list on their phone.
|
|
@@ -1259,8 +1259,8 @@ class MessageSender {
|
|
|
1259
1259
|
: Promise.resolve([]),
|
|
1260
1260
|
// Our own linked devices, in the address family the stanza is routed with.
|
|
1261
1261
|
// ensureOwnDeviceSessions hands back @s.whatsapp.net JIDs, which cannot go
|
|
1262
|
-
// into a stanza carrying addressing_mode='lid'; both helpers
|
|
1263
|
-
//
|
|
1262
|
+
// into a stanza carrying addressing_mode='lid'; both helpers leave out the
|
|
1263
|
+
// device we are, which is device 0 only while we are the primary.
|
|
1264
1264
|
useLid
|
|
1265
1265
|
? this._devMgr.ensureOwnLidDeviceSessions(myLidUser, ownPhone, this._signal)
|
|
1266
1266
|
: this._devMgr.ensureOwnDeviceSessions(ownPhone, this._signal)
|
|
@@ -1275,8 +1275,17 @@ class MessageSender {
|
|
|
1275
1275
|
const s = String(jid);
|
|
1276
1276
|
return s.endsWith('@hosted') || s.endsWith('@hosted.lid');
|
|
1277
1277
|
};
|
|
1278
|
+
// The sending device counts in the enumeration even though it is not a
|
|
1279
|
+
// recipient — and that is *this* device, not device 0. On a companion link
|
|
1280
|
+
// the bare JID names the owner's phone, which arrives through ownTargets
|
|
1281
|
+
// like every other device; adding it here again while leaving our own slot
|
|
1282
|
+
// out would hash a set the server never computes.
|
|
1283
|
+
const ownSelfJid = (useLid
|
|
1284
|
+
? this._client._ownDeviceLidJid()
|
|
1285
|
+
: this._client._ownDeviceJid()) || selfJid;
|
|
1286
|
+
|
|
1278
1287
|
const enumeratedDevices = [...new Set(
|
|
1279
|
-
[...memberDevices, ...lidDevices, ...ownTargets,
|
|
1288
|
+
[...memberDevices, ...lidDevices, ...ownTargets, ownSelfJid]
|
|
1280
1289
|
)].filter(jid => !isHostedJid(jid));
|
|
1281
1290
|
|
|
1282
1291
|
// The participant hash the server checks the fan-out against. whatsmeow puts
|
package/lib/webproto.js
CHANGED
|
@@ -148,6 +148,9 @@ function encodeAppVersion(v) {
|
|
|
148
148
|
]);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
// Exactly the fields the web client sets, and no others. `manufacturer` is
|
|
152
|
+
// deliberately absent: the reference never populates it for a web UserAgent,
|
|
153
|
+
// and an extra field is a difference the server can notice.
|
|
151
154
|
function encodeWebUserAgent(opts) {
|
|
152
155
|
const version = opts.version || [2, 3000, 1];
|
|
153
156
|
const browser = opts.browser || ['Ubuntu', 'Chrome', '120.0.0.0'];
|
|
@@ -158,7 +161,6 @@ function encodeWebUserAgent(opts) {
|
|
|
158
161
|
stringField(3, '000'), // mcc
|
|
159
162
|
stringField(4, '000'), // mnc
|
|
160
163
|
stringField(5, '0.1'), // osVersion
|
|
161
|
-
stringField(6, 'Desktop'), // manufacturer
|
|
162
164
|
stringField(7, 'Desktop'), // device
|
|
163
165
|
stringField(8, '0.1'), // osBuildNumber
|
|
164
166
|
varintField(10, 0), // releaseChannel = RELEASE
|