whalibmob 5.5.22 → 5.5.23
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/.env.example +49 -0
- package/README.md +151 -0
- package/cli.js +13 -7
- package/index.js +12 -1
- package/lib/DeviceManager.js +69 -41
- package/lib/Registration.js +10 -5
- package/lib/auth-utils.js +693 -0
- package/lib/messages/MessageSender.js +34 -19
- package/lib/noise.js +9 -4
- package/package.json +1 -1
package/.env.example
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
# whalibmob — Device Emulation Configuration
|
|
3
|
+
# Copy this file to .env in your project root and set the values you need.
|
|
4
|
+
# All variables are optional; defaults emulate an iPhone 15 Pro running iOS 17.
|
|
5
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
# Operating system to emulate. Accepted values: ios | android
|
|
8
|
+
# Default: ios
|
|
9
|
+
# WA_OS=android
|
|
10
|
+
|
|
11
|
+
# Named device profile.
|
|
12
|
+
#
|
|
13
|
+
# iOS profiles (WA_OS=ios):
|
|
14
|
+
# iphone_15_pro, iphone_15, iphone_14_pro, iphone_14, iphone_13_pro,
|
|
15
|
+
# iphone_13, iphone_12_pro, iphone_12, iphone_11_pro, iphone_11,
|
|
16
|
+
# iphone_se3, iphone_xs
|
|
17
|
+
#
|
|
18
|
+
# Android profiles (WA_OS=android):
|
|
19
|
+
# pixel_8_pro, pixel_8, pixel_7, pixel_7a,
|
|
20
|
+
# samsung_s24_ultra, samsung_s24, samsung_s23_ultra, samsung_s23, samsung_a55,
|
|
21
|
+
# oneplus_12, oneplus_11, xiaomi_14, xiaomi_13, oppo_find_x7, realme_gt5
|
|
22
|
+
#
|
|
23
|
+
# Default: iphone_15_pro (or pixel_8_pro when WA_OS=android)
|
|
24
|
+
# WA_DEVICE=pixel_8_pro
|
|
25
|
+
|
|
26
|
+
# ── Custom device overrides (applied on top of the selected profile) ─────────
|
|
27
|
+
# Use these to fine-tune any field without creating a new profile.
|
|
28
|
+
|
|
29
|
+
# WA_DEVICE_MODEL=SM-S928B
|
|
30
|
+
# WA_DEVICE_MANUFACTURER=samsung
|
|
31
|
+
# WA_DEVICE_OS_VERSION=14
|
|
32
|
+
# WA_DEVICE_BUILD=UP1A.231005.007
|
|
33
|
+
# WA_DEVICE_MODEL_ID=samsung-sm-s928b
|
|
34
|
+
|
|
35
|
+
# ── Version & token overrides ─────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
# Pin the WhatsApp version string instead of fetching the latest from the store.
|
|
38
|
+
# Format: 2.x.x.x (four-part)
|
|
39
|
+
# WA_VERSION=2.24.13.80
|
|
40
|
+
|
|
41
|
+
# Override the static token used in registration token computation.
|
|
42
|
+
# Only needed if WhatsApp rotates the bundled token.
|
|
43
|
+
# WA_STATIC_TOKEN=Y29Cs6AVNR2bj5PBeKSYFd1nAKuvNQ3h
|
|
44
|
+
|
|
45
|
+
# ── Proxy / Tor ───────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
# Route registration HTTP traffic through a SOCKS5 proxy or Tor.
|
|
48
|
+
# TOR_PROXY=socks5://127.0.0.1:9050
|
|
49
|
+
# SOCKS_PROXY=socks5://user:pass@proxy.example.com:1080
|
package/README.md
CHANGED
|
@@ -6,6 +6,20 @@
|
|
|
6
6
|
> [!CAUTION]
|
|
7
7
|
> Use a dedicated phone number with this library. Connecting with a number that is already active on a real device will cause WhatsApp to log that device out.
|
|
8
8
|
|
|
9
|
+
> [!CAUTION]
|
|
10
|
+
> Whalibmob now It needs to be rewritten because WhatsApp mobile and has changed the protocol lately and now whalibmob is in testing and some updates by Me Any pull request is accepted.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
> [!IMPORTANT]
|
|
14
|
+
> If you like what I do and want to support me I can leave you here my Crypto usdc address for any donation and support any Small donation is accepted because the WhatsApp protocol changes very often : "0x8AD64F47a715eC24DeF193FBb9aC64d4E857f0f3"
|
|
15
|
+
|
|
16
|
+
Usdc ethereum network.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
9
23
|
> [!IMPORTANT]
|
|
10
24
|
> This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with WhatsApp or any of its subsidiaries or affiliates. "WhatsApp" and related names are registered trademarks of their respective owners. Use at your own discretion.
|
|
11
25
|
|
|
@@ -102,6 +116,11 @@ npm install -g whalibmob
|
|
|
102
116
|
- [Register a New Number](#register-a-new-number)
|
|
103
117
|
- [Connect](#connect)
|
|
104
118
|
- [Saving & Restoring Sessions](#saving--restoring-sessions)
|
|
119
|
+
- [Signal Store Utilities](#signal-store-utilities)
|
|
120
|
+
- [makeCacheableSignalKeyStore](#makecacheablesignalkeystore)
|
|
121
|
+
- [addTransactionCapability](#addtransactioncapability)
|
|
122
|
+
- [assertMeId](#assertmeid)
|
|
123
|
+
- [initAuthCreds](#initauthcreds)
|
|
105
124
|
- [Handling Events](#handling-events)
|
|
106
125
|
- [Example to Start](#example-to-start)
|
|
107
126
|
- [All Events](#all-events)
|
|
@@ -267,6 +286,138 @@ await client.init('919634847671')
|
|
|
267
286
|
> [!NOTE]
|
|
268
287
|
> Each phone number uses its own session file. The library handles Signal Protocol key persistence automatically.
|
|
269
288
|
|
|
289
|
+
## Signal Store Utilities
|
|
290
|
+
|
|
291
|
+
`auth-utils` is a collection of optional helpers for power users who manage their own `SignalStore` instances directly (e.g. custom storage backends, multi-account servers).
|
|
292
|
+
|
|
293
|
+
```js
|
|
294
|
+
const {
|
|
295
|
+
makeCacheableSignalKeyStore,
|
|
296
|
+
addTransactionCapability,
|
|
297
|
+
assertMeId,
|
|
298
|
+
initAuthCreds
|
|
299
|
+
} = require('whalibmob')
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### `makeCacheableSignalKeyStore`
|
|
303
|
+
|
|
304
|
+
Wraps a `SignalStore` with an in-memory NodeCache layer (5-minute TTL). All `get` calls for `sessions`, `preKeys`, `signedPreKeys`, and `identities` are served from cache on subsequent accesses. Writes invalidate the cache automatically.
|
|
305
|
+
|
|
306
|
+
`useClones` is set to `false` so that `SessionRecord` objects — which carry internal state and methods — are returned by reference and never deep-cloned.
|
|
307
|
+
|
|
308
|
+
The wrapper also forwards `transaction()` and `isInTransaction()` calls to the underlying store when present, making it safe to stack with `addTransactionCapability`.
|
|
309
|
+
|
|
310
|
+
```js
|
|
311
|
+
const { SignalStore } = require('whalibmob')
|
|
312
|
+
const { makeCacheableSignalKeyStore } = require('whalibmob')
|
|
313
|
+
|
|
314
|
+
const store = new SignalStore(/* ... */)
|
|
315
|
+
const cached = makeCacheableSignalKeyStore(store)
|
|
316
|
+
|
|
317
|
+
// reads hit cache after first access
|
|
318
|
+
const session = await cached.getSession('919634847671@s.whatsapp.net:0')
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**When to use:** whenever your `SignalStore` is backed by a remote or disk-based store (database, Redis, file system) and you want to reduce repeated lookups for sessions that haven't changed between sends.
|
|
322
|
+
|
|
323
|
+
### `addTransactionCapability`
|
|
324
|
+
|
|
325
|
+
Wraps a `SignalStore` with batched-write (transaction) semantics. During a transaction all writes are buffered in memory; they are flushed to the underlying store atomically when `commit()` is called at the end of the transaction.
|
|
326
|
+
|
|
327
|
+
Uses `AsyncLocalStorage` to propagate transaction context across async call chains, and a per-key-type `Mutex` with reference-counting to serialize concurrent writers safely.
|
|
328
|
+
|
|
329
|
+
```js
|
|
330
|
+
const { addTransactionCapability, makeCacheableSignalKeyStore } = require('whalibmob')
|
|
331
|
+
|
|
332
|
+
// recommended: cache first, then transactions on top
|
|
333
|
+
const base = new SignalStore(/* ... */)
|
|
334
|
+
const cached = makeCacheableSignalKeyStore(base)
|
|
335
|
+
const txnStore = addTransactionCapability(cached)
|
|
336
|
+
|
|
337
|
+
// inside a send flow
|
|
338
|
+
await txnStore.transaction(async () => {
|
|
339
|
+
// all writes are buffered
|
|
340
|
+
await txnStore.setSession('919634847671@s.whatsapp.net:0', sessionRecord)
|
|
341
|
+
await txnStore.setPreKey(1, preKeyPair)
|
|
342
|
+
// commit is called automatically at the end of the transaction callback
|
|
343
|
+
})
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Stacking order matters: put `makeCacheableSignalKeyStore` below `addTransactionCapability` so that the cache always sees the committed state.
|
|
347
|
+
|
|
348
|
+
**When to use:** for high-throughput servers that send to many recipients concurrently and need to batch Signal key writes into a single atomic flush per message.
|
|
349
|
+
|
|
350
|
+
### `assertMeId`
|
|
351
|
+
|
|
352
|
+
Validates that a store object has a registered phone number and returns the canonical `@s.whatsapp.net` JID. Throws an `Error` if the store lacks a `phoneNumber` or has `registered !== true`.
|
|
353
|
+
|
|
354
|
+
```js
|
|
355
|
+
const { assertMeId } = require('whalibmob')
|
|
356
|
+
|
|
357
|
+
const store = loadStore(sessFile)
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
const jid = assertMeId(store)
|
|
361
|
+
// jid === '919634847671@s.whatsapp.net'
|
|
362
|
+
console.log('account JID:', jid)
|
|
363
|
+
} catch (err) {
|
|
364
|
+
console.error('store is not registered:', err.message)
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**When to use:** as a guard before calling `client.init()` to give a clear error message when a corrupted or unregistered session file is accidentally loaded.
|
|
369
|
+
|
|
370
|
+
### `initAuthCreds`
|
|
371
|
+
|
|
372
|
+
Creates a fresh credential store for the given phone number. Functionally equivalent to `createNewStore` but also initialises the Baileys-compatible extra fields that the library expects for account sync: `nextPreKeyId`, `firstUnuploadedPreKeyId`, `accountSyncCounter`, `accountSettings`, and `advSecretKey`.
|
|
373
|
+
|
|
374
|
+
```js
|
|
375
|
+
const { initAuthCreds, saveStore } = require('whalibmob')
|
|
376
|
+
const path = require('path')
|
|
377
|
+
const fs = require('fs')
|
|
378
|
+
|
|
379
|
+
const phone = '919634847671'
|
|
380
|
+
const sessDir = path.join(process.env.HOME, '.waSession')
|
|
381
|
+
const sessFile = path.join(sessDir, phone + '.json')
|
|
382
|
+
|
|
383
|
+
fs.mkdirSync(sessDir, { recursive: true })
|
|
384
|
+
|
|
385
|
+
const store = initAuthCreds(phone)
|
|
386
|
+
saveStore(store, sessFile)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
This is the function used internally by the CLI for all new session creation. Prefer it over `createNewStore` for forward compatibility.
|
|
390
|
+
|
|
391
|
+
> [!NOTE]
|
|
392
|
+
> `initAuthCreds` and `createNewStore` produce equivalent stores for all current library operations. The additional fields from `initAuthCreds` are there for future-proofing and interoperability.
|
|
393
|
+
|
|
394
|
+
### Recommended Stacking Pattern
|
|
395
|
+
|
|
396
|
+
For a production multi-account server:
|
|
397
|
+
|
|
398
|
+
```js
|
|
399
|
+
const {
|
|
400
|
+
SignalStore,
|
|
401
|
+
makeCacheableSignalKeyStore,
|
|
402
|
+
addTransactionCapability,
|
|
403
|
+
initAuthCreds,
|
|
404
|
+
saveStore,
|
|
405
|
+
loadStore
|
|
406
|
+
} = require('whalibmob')
|
|
407
|
+
|
|
408
|
+
// 1. load or create the credential store
|
|
409
|
+
let store = loadStore(sessFile) || initAuthCreds(phone)
|
|
410
|
+
|
|
411
|
+
// 2. build the layered Signal key store
|
|
412
|
+
const signalStore = new SignalStore(store)
|
|
413
|
+
const cachedStore = makeCacheableSignalKeyStore(signalStore)
|
|
414
|
+
const txnStore = addTransactionCapability(cachedStore)
|
|
415
|
+
|
|
416
|
+
// 3. pass to the client (advanced usage — most users should use WhalibmobClient directly)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
For standard usage, `WhalibmobClient` handles all of this internally. These helpers are for advanced scenarios where you need direct control over Signal key storage.
|
|
420
|
+
|
|
270
421
|
## Handling Events
|
|
271
422
|
|
|
272
423
|
whalibmob uses the EventEmitter syntax for events.
|
package/cli.js
CHANGED
|
@@ -35,6 +35,8 @@ const {
|
|
|
35
35
|
loadStore
|
|
36
36
|
} = require('./lib/Client');
|
|
37
37
|
|
|
38
|
+
const { assertMeId, initAuthCreds } = require('./lib/auth-utils');
|
|
39
|
+
|
|
38
40
|
const VERSION = '5.1.21';
|
|
39
41
|
|
|
40
42
|
// ─── output helpers ───────────────────────────────────────────────────────────
|
|
@@ -435,14 +437,18 @@ async function handleLine(line) {
|
|
|
435
437
|
if (_client) { try { _client.disconnect(); } catch (_) {} }
|
|
436
438
|
process.exit(0);
|
|
437
439
|
|
|
438
|
-
case '/session':
|
|
440
|
+
case '/session': {
|
|
439
441
|
if (!_client || !_client.store) { fail('not connected'); break; }
|
|
442
|
+
let _meJid = '—';
|
|
443
|
+
try { _meJid = assertMeId(_client.store); } catch (_) {}
|
|
440
444
|
hr();
|
|
441
445
|
kv('phone', _client.store.phoneNumber);
|
|
446
|
+
kv('jid', _meJid);
|
|
442
447
|
kv('name', _client.store.pushName || _client.store.name || '—');
|
|
443
448
|
kv('session', _sessDir);
|
|
444
449
|
hr();
|
|
445
450
|
break;
|
|
451
|
+
}
|
|
446
452
|
|
|
447
453
|
case '/connect': {
|
|
448
454
|
const ph = p[1];
|
|
@@ -1153,7 +1159,7 @@ async function handleLine(line) {
|
|
|
1153
1159
|
const sessFile = path.join(_sessDir, `${ph}.json`);
|
|
1154
1160
|
let store = loadStore(sessFile);
|
|
1155
1161
|
if (!store) {
|
|
1156
|
-
store =
|
|
1162
|
+
store = initAuthCreds(ph);
|
|
1157
1163
|
saveStore(store, sessFile);
|
|
1158
1164
|
} else if (!store.codePending && !store.registered) {
|
|
1159
1165
|
// Only check /exist when keys were never used to request a code.
|
|
@@ -1162,7 +1168,7 @@ async function handleLine(line) {
|
|
|
1162
1168
|
const fresh = await assertRegistrationKeys(store, waVersion);
|
|
1163
1169
|
if (!fresh) {
|
|
1164
1170
|
out(' device keys already registered — generating new keys...');
|
|
1165
|
-
store =
|
|
1171
|
+
store = initAuthCreds(ph);
|
|
1166
1172
|
saveStore(store, sessFile);
|
|
1167
1173
|
out(' new keys saved — proceed with code below');
|
|
1168
1174
|
}
|
|
@@ -1180,7 +1186,7 @@ async function handleLine(line) {
|
|
|
1180
1186
|
const code = p[3];
|
|
1181
1187
|
if (!ph || !code) { fail('usage: /reg confirm <phone> <code>'); break; }
|
|
1182
1188
|
const file = path.join(_sessDir, `${ph}.json`);
|
|
1183
|
-
const store = loadStore(file) ||
|
|
1189
|
+
const store = loadStore(file) || initAuthCreds(ph);
|
|
1184
1190
|
out('verifying...');
|
|
1185
1191
|
const r = await verifyCode(store, code);
|
|
1186
1192
|
if (r && (r.status === 'ok' || r.status === 'sent' || r.status === 'verified')) {
|
|
@@ -1477,7 +1483,7 @@ async function main() {
|
|
|
1477
1483
|
let store = loadStore(sessFile);
|
|
1478
1484
|
if (!store) {
|
|
1479
1485
|
// Brand new — generate fresh keys, save immediately, no need to check /exist
|
|
1480
|
-
store =
|
|
1486
|
+
store = initAuthCreds(ph);
|
|
1481
1487
|
saveStore(store, sessFile);
|
|
1482
1488
|
} else if (!store.codePending && !store.registered) {
|
|
1483
1489
|
// Existing store but code was never sent and not registered — check if
|
|
@@ -1488,7 +1494,7 @@ async function main() {
|
|
|
1488
1494
|
const fresh = await assertRegistrationKeys(store, waVersion);
|
|
1489
1495
|
if (!fresh) {
|
|
1490
1496
|
out(' device keys already registered — generating new keys...');
|
|
1491
|
-
store =
|
|
1497
|
+
store = initAuthCreds(ph);
|
|
1492
1498
|
saveStore(store, sessFile);
|
|
1493
1499
|
}
|
|
1494
1500
|
}
|
|
@@ -1518,7 +1524,7 @@ async function main() {
|
|
|
1518
1524
|
if (!ph) { fail('phone number required'); process.exit(1); }
|
|
1519
1525
|
if (!code) { fail('--code is required'); process.exit(1); }
|
|
1520
1526
|
const file = path.join(_sessDir, `${ph}.json`);
|
|
1521
|
-
const store = loadStore(file) ||
|
|
1527
|
+
const store = loadStore(file) || initAuthCreds(ph);
|
|
1522
1528
|
out('verifying code for +' + ph + '...');
|
|
1523
1529
|
try {
|
|
1524
1530
|
const r = await verifyCode(store, code);
|
package/index.js
CHANGED
|
@@ -11,6 +11,12 @@ const { SenderKeyStore, SenderKeyCrypto } = require('./lib/signal/SenderKey');
|
|
|
11
11
|
const { DeviceManager } = require('./lib/DeviceManager');
|
|
12
12
|
const { encryptMedia, decryptMedia, uploadMedia, downloadMedia } = require('./lib/MediaService');
|
|
13
13
|
const { MessageSender, makeJid, generateMessageId } = require('./lib/messages/MessageSender');
|
|
14
|
+
const {
|
|
15
|
+
makeCacheableSignalKeyStore,
|
|
16
|
+
addTransactionCapability,
|
|
17
|
+
assertMeId,
|
|
18
|
+
initAuthCreds
|
|
19
|
+
} = require('./lib/auth-utils');
|
|
14
20
|
|
|
15
21
|
module.exports = {
|
|
16
22
|
WhalibmobClient,
|
|
@@ -48,5 +54,10 @@ module.exports = {
|
|
|
48
54
|
// Message helpers
|
|
49
55
|
MessageSender,
|
|
50
56
|
makeJid,
|
|
51
|
-
generateMessageId
|
|
57
|
+
generateMessageId,
|
|
58
|
+
// Auth utilities (mirrors Baileys' auth-utils)
|
|
59
|
+
makeCacheableSignalKeyStore,
|
|
60
|
+
addTransactionCapability,
|
|
61
|
+
assertMeId,
|
|
62
|
+
initAuthCreds
|
|
52
63
|
};
|
package/lib/DeviceManager.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { BinaryNode }
|
|
3
|
+
const { BinaryNode } = require('./BinaryNode');
|
|
4
|
+
const { NodeCache } = require('@cacheable/node-cache');
|
|
5
|
+
|
|
6
|
+
const DEVICE_CACHE_TTL = '5m';
|
|
4
7
|
|
|
5
8
|
// ─── JID helpers ─────────────────────────────────────────────────────────────
|
|
6
9
|
|
|
@@ -131,12 +134,37 @@ function parseBundleFromUserNode(userNode) {
|
|
|
131
134
|
class DeviceManager {
|
|
132
135
|
constructor(client) {
|
|
133
136
|
this._client = client;
|
|
134
|
-
// phone →
|
|
135
|
-
this._deviceCache = new
|
|
137
|
+
// phone → deviceId[] (TTL-based: expires after 5 min, triggers fresh usync)
|
|
138
|
+
this._deviceCache = new NodeCache({ ttl: DEVICE_CACHE_TTL });
|
|
136
139
|
// phone → Promise<jids[]> (in-flight usync dedup)
|
|
137
140
|
this._usyncInflight = new Map();
|
|
138
|
-
// own device list cache (
|
|
139
|
-
this._ownDeviceJids
|
|
141
|
+
// own device list cache + expiry timestamp (refreshed every 5 min)
|
|
142
|
+
this._ownDeviceJids = null;
|
|
143
|
+
this._ownDeviceExpiry = 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── NodeCache helper methods ─────────────────────────────────────────────
|
|
147
|
+
// _deviceCache stores deviceId arrays (not Sets) because NodeCache serialises
|
|
148
|
+
// values and Sets don't round-trip reliably. All callers use these helpers.
|
|
149
|
+
|
|
150
|
+
_dcHas(key) { return this._deviceCache.has(key); }
|
|
151
|
+
_dcGet(key) { return this._deviceCache.get(key) || null; } // number[] | null
|
|
152
|
+
_dcSet(key, arr) { this._deviceCache.set(key, arr); }
|
|
153
|
+
_dcAdd(key, id) {
|
|
154
|
+
const arr = this._deviceCache.get(key) || [];
|
|
155
|
+
if (!arr.includes(id)) arr.push(id);
|
|
156
|
+
this._deviceCache.set(key, arr);
|
|
157
|
+
}
|
|
158
|
+
_dcEnsure(key) {
|
|
159
|
+
if (!this._deviceCache.has(key)) this._deviceCache.set(key, []);
|
|
160
|
+
}
|
|
161
|
+
_dcDel(keys) {
|
|
162
|
+
for (const k of keys) this._deviceCache.del(k);
|
|
163
|
+
}
|
|
164
|
+
_dcFlush() {
|
|
165
|
+
this._deviceCache.flushAll();
|
|
166
|
+
this._ownDeviceJids = null;
|
|
167
|
+
this._ownDeviceExpiry = 0;
|
|
140
168
|
}
|
|
141
169
|
|
|
142
170
|
// ─── Fetch pre-key bundles for a list of JIDs via encrypt IQ ───────────────
|
|
@@ -222,8 +250,8 @@ class DeviceManager {
|
|
|
222
250
|
// Only truly unknown phones trigger a network IQ.
|
|
223
251
|
|
|
224
252
|
async _usyncGetDevices(phones) {
|
|
225
|
-
// Fast path: all phones cached
|
|
226
|
-
const uncached = phones.filter(p => !this.
|
|
253
|
+
// Fast path: all phones cached (TTL-based: NodeCache returns null for expired entries)
|
|
254
|
+
const uncached = phones.filter(p => !this._dcHas(p));
|
|
227
255
|
if (uncached.length === 0) {
|
|
228
256
|
return this._jidsFromCache(phones);
|
|
229
257
|
}
|
|
@@ -250,8 +278,8 @@ class DeviceManager {
|
|
|
250
278
|
_jidsFromCache(phones) {
|
|
251
279
|
const jids = [];
|
|
252
280
|
for (const p of phones) {
|
|
253
|
-
const devices = this.
|
|
254
|
-
if (devices && devices.
|
|
281
|
+
const devices = this._dcGet(p);
|
|
282
|
+
if (devices && devices.length > 0) {
|
|
255
283
|
for (const dev of devices) jids.push(makeDeviceJid(p, dev));
|
|
256
284
|
} else {
|
|
257
285
|
jids.push(makeDeviceJid(p, 0));
|
|
@@ -374,9 +402,7 @@ class DeviceManager {
|
|
|
374
402
|
const devicesNode = findChild(userNode, 'devices');
|
|
375
403
|
const deviceListNode = devicesNode ? findChild(devicesNode, 'device-list') : null;
|
|
376
404
|
|
|
377
|
-
|
|
378
|
-
this._deviceCache.set(cachePhone, new Set());
|
|
379
|
-
}
|
|
405
|
+
this._dcEnsure(cachePhone);
|
|
380
406
|
|
|
381
407
|
if (deviceListNode && Array.isArray(deviceListNode.content)) {
|
|
382
408
|
for (const devNode of deviceListNode.content) {
|
|
@@ -385,13 +411,13 @@ class DeviceManager {
|
|
|
385
411
|
const devId = devNode.attrs && devNode.attrs.id != null
|
|
386
412
|
? parseInt(String(devNode.attrs.id), 10)
|
|
387
413
|
: 0;
|
|
388
|
-
this.
|
|
414
|
+
this._dcAdd(cachePhone, devId);
|
|
389
415
|
}
|
|
390
416
|
process.stderr.write('[DBG] USYNC_DEVICES phone=' + cachePhone +
|
|
391
|
-
' ids=[' +
|
|
417
|
+
' ids=[' + (this._dcGet(cachePhone) || []).join(',') + ']\n');
|
|
392
418
|
} else {
|
|
393
419
|
// No device-list in response — fall back to device 0
|
|
394
|
-
this.
|
|
420
|
+
this._dcAdd(cachePhone, 0);
|
|
395
421
|
process.stderr.write('[DBG] USYNC_NO_DEVLIST phone=' + cachePhone + ' → fallback id=0\n');
|
|
396
422
|
}
|
|
397
423
|
}
|
|
@@ -409,8 +435,8 @@ class DeviceManager {
|
|
|
409
435
|
|
|
410
436
|
// For phones with no usync response at all, cache device 0 so we don't re-query
|
|
411
437
|
for (const p of phones) {
|
|
412
|
-
if (!this.
|
|
413
|
-
this.
|
|
438
|
+
if (!this._dcHas(p)) {
|
|
439
|
+
this._dcSet(p, [0]);
|
|
414
440
|
process.stderr.write('[DBG] USYNC_FALLBACK phone=' + p + '\n');
|
|
415
441
|
}
|
|
416
442
|
}
|
|
@@ -476,19 +502,23 @@ class DeviceManager {
|
|
|
476
502
|
const lidJid = `${lidUser}@lid`;
|
|
477
503
|
const cacheKey = `lid:${lidUser}`;
|
|
478
504
|
|
|
479
|
-
// skipUsync=true:
|
|
480
|
-
//
|
|
505
|
+
// skipUsync=true: skip usync ONLY if we already have a cache hit (i.e. we
|
|
506
|
+
// fetched this LID's devices in the last 5 min). On a cache miss we MUST
|
|
507
|
+
// run a real usync even with skipUsync=true, otherwise linked devices (tablets,
|
|
508
|
+
// desktop, web) are silently ignored and only device 0 (primary phone) is used.
|
|
481
509
|
if (skipUsync) {
|
|
482
|
-
if (!this.
|
|
483
|
-
|
|
484
|
-
|
|
510
|
+
if (!this._dcHas(cacheKey)) {
|
|
511
|
+
process.stderr.write('[DBG] LID_SKIP_USYNC_CACHE_MISS lidUser=' + lidUser + ' → real usync\n');
|
|
512
|
+
await this._doUsyncIqByJid(lidJid, cacheKey, lidUser);
|
|
513
|
+
} else {
|
|
514
|
+
process.stderr.write('[DBG] LID_SKIP_USYNC_HIT lidUser=' + lidUser + ' → cache\n');
|
|
485
515
|
}
|
|
486
|
-
} else if (!this.
|
|
516
|
+
} else if (!this._dcHas(cacheKey)) {
|
|
487
517
|
await this._doUsyncIqByJid(lidJid, cacheKey, lidUser);
|
|
488
518
|
}
|
|
489
519
|
|
|
490
|
-
const deviceIds = this.
|
|
491
|
-
const deviceJids =
|
|
520
|
+
const deviceIds = this._dcGet(cacheKey) || [0];
|
|
521
|
+
const deviceJids = deviceIds.map(d => makeDeviceJid(lidUser, d, 'lid'));
|
|
492
522
|
|
|
493
523
|
process.stderr.write('[DBG] LID_DEVICES lidUser=' + lidUser +
|
|
494
524
|
' deviceJids=[' + deviceJids.join(',') + ']\n');
|
|
@@ -635,9 +665,7 @@ class DeviceManager {
|
|
|
635
665
|
return null;
|
|
636
666
|
});
|
|
637
667
|
|
|
638
|
-
|
|
639
|
-
this._deviceCache.set(cacheKey, new Set());
|
|
640
|
-
}
|
|
668
|
+
this._dcEnsure(cacheKey);
|
|
641
669
|
|
|
642
670
|
if (response) {
|
|
643
671
|
const usyncNode = findChild(response, 'usync');
|
|
@@ -655,22 +683,22 @@ class DeviceManager {
|
|
|
655
683
|
if (!devNode || devNode.description !== 'device') continue;
|
|
656
684
|
const devId = devNode.attrs && devNode.attrs.id != null
|
|
657
685
|
? parseInt(String(devNode.attrs.id), 10) : 0;
|
|
658
|
-
this.
|
|
686
|
+
this._dcAdd(cacheKey, devId);
|
|
659
687
|
}
|
|
660
688
|
process.stderr.write('[DBG] USYNC_LID_DEVICES jid=' + jid +
|
|
661
|
-
' ids=[' +
|
|
689
|
+
' ids=[' + (this._dcGet(cacheKey) || []).join(',') + ']\n');
|
|
662
690
|
} else {
|
|
663
|
-
this.
|
|
691
|
+
this._dcAdd(cacheKey, 0);
|
|
664
692
|
process.stderr.write('[DBG] USYNC_LID_NO_DEVLIST jid=' + jid + ' → fallback id=0\n');
|
|
665
693
|
}
|
|
666
694
|
}
|
|
667
695
|
} else {
|
|
668
696
|
process.stderr.write('[DBG] USYNC_LID_NO_LIST jid=' + jid + '\n');
|
|
669
|
-
this.
|
|
697
|
+
this._dcAdd(cacheKey, 0);
|
|
670
698
|
}
|
|
671
699
|
} else {
|
|
672
700
|
process.stderr.write('[DBG] USYNC_LID_NULL_RESP jid=' + jid + '\n');
|
|
673
|
-
this.
|
|
701
|
+
this._dcAdd(cacheKey, 0);
|
|
674
702
|
}
|
|
675
703
|
}
|
|
676
704
|
|
|
@@ -681,15 +709,16 @@ class DeviceManager {
|
|
|
681
709
|
// from cache — zero extra network round-trips.
|
|
682
710
|
//
|
|
683
711
|
async ensureOwnDeviceSessions(ownPhone, signalProto, allowPkmsg = true) {
|
|
684
|
-
//
|
|
685
|
-
if (this._ownDeviceJids !== null) {
|
|
712
|
+
// Use cached own device list if it hasn't expired (5-min TTL matches _deviceCache)
|
|
713
|
+
if (this._ownDeviceJids !== null && Date.now() < this._ownDeviceExpiry) {
|
|
686
714
|
const others = this._ownDeviceJids.filter(j => stripUser(j).device !== 0);
|
|
687
715
|
return this._ensureSessions(others, signalProto, allowPkmsg);
|
|
688
716
|
}
|
|
689
717
|
|
|
690
|
-
//
|
|
691
|
-
const deviceJids
|
|
692
|
-
this._ownDeviceJids
|
|
718
|
+
// Cache miss or expired: re-query usync for own devices
|
|
719
|
+
const deviceJids = await this._usyncGetDevices([ownPhone]);
|
|
720
|
+
this._ownDeviceJids = deviceJids;
|
|
721
|
+
this._ownDeviceExpiry = Date.now() + 5 * 60 * 1000;
|
|
693
722
|
|
|
694
723
|
const others = deviceJids.filter(j => stripUser(j).device !== 0);
|
|
695
724
|
return this._ensureSessions(others, signalProto, allowPkmsg);
|
|
@@ -730,10 +759,9 @@ class DeviceManager {
|
|
|
730
759
|
// ─── Invalidate cached device list (used on phash mismatch / 421 retry) ───
|
|
731
760
|
clearCache(phones) {
|
|
732
761
|
if (!phones || phones.length === 0) {
|
|
733
|
-
this.
|
|
734
|
-
this._ownDeviceJids = null;
|
|
762
|
+
this._dcFlush();
|
|
735
763
|
} else {
|
|
736
|
-
|
|
764
|
+
this._dcDel(phones);
|
|
737
765
|
}
|
|
738
766
|
}
|
|
739
767
|
|
package/lib/Registration.js
CHANGED
|
@@ -370,6 +370,7 @@ function toBase64Url(buf) {
|
|
|
370
370
|
|
|
371
371
|
function buildPayload(store, waVersion, useToken, extraPairs) {
|
|
372
372
|
const { cc, national } = parsePhone(store.phoneNumber);
|
|
373
|
+
const meta = getCountryMeta(cc);
|
|
373
374
|
const token = useToken ? computeToken(waVersion, national) : null;
|
|
374
375
|
const fdid = store.fdid.toUpperCase();
|
|
375
376
|
|
|
@@ -377,8 +378,8 @@ function buildPayload(store, waVersion, useToken, extraPairs) {
|
|
|
377
378
|
'cc', cc,
|
|
378
379
|
'in', national,
|
|
379
380
|
'rc', String(RELEASE_CHANNEL),
|
|
380
|
-
'lg',
|
|
381
|
-
'lc',
|
|
381
|
+
'lg', meta.lg,
|
|
382
|
+
'lc', meta.lc,
|
|
382
383
|
'authkey', toBase64Url(stripKeyPrefix(store.noiseKeyPair.public)),
|
|
383
384
|
'e_regid', toBase64Url(intToBytes(store.registrationId, 4)),
|
|
384
385
|
'e_keytype', toBase64Url(Buffer.from([SIGNAL_KEY_TYPE])),
|
|
@@ -601,11 +602,15 @@ async function requestSmsCode(store, method) {
|
|
|
601
602
|
const fallbackMethod = method === 'wa_old' ? 'sms' : (method === 'sms' ? 'wa_old' : 'sms');
|
|
602
603
|
let autoFallbackDone = false;
|
|
603
604
|
|
|
605
|
+
// Derive real MCC/MNC from the phone number being registered.
|
|
606
|
+
const { cc: _regCc } = parsePhone(store.phoneNumber);
|
|
607
|
+
const _regMeta = getCountryMeta(_regCc);
|
|
608
|
+
|
|
604
609
|
async function _tryMethod(m) {
|
|
605
610
|
const extra = [
|
|
606
611
|
'method', m,
|
|
607
|
-
'sim_mcc',
|
|
608
|
-
'sim_mnc',
|
|
612
|
+
'sim_mcc', _regMeta.mcc,
|
|
613
|
+
'sim_mnc', _regMeta.mnc,
|
|
609
614
|
'reason', '',
|
|
610
615
|
'cellular_strength', '1'
|
|
611
616
|
];
|
|
@@ -706,4 +711,4 @@ async function verifyCode(store, code) {
|
|
|
706
711
|
throw new Error(`Verification failed: ${reason || JSON.stringify(result)}`);
|
|
707
712
|
}
|
|
708
713
|
|
|
709
|
-
module.exports = { checkIfRegistered, checkNumberStatus, requestSmsCode, verifyCode, fetchIosVersion, fetchAndroidVersion, fetchWaVersion, parsePhone, assertRegistrationKeys };
|
|
714
|
+
module.exports = { checkIfRegistered, checkNumberStatus, requestSmsCode, verifyCode, fetchIosVersion, fetchAndroidVersion, fetchWaVersion, parsePhone, getCountryMeta, assertRegistrationKeys };
|