whalibmob 5.5.21 → 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/README.md +151 -0
- package/cli.js +13 -7
- package/index.js +12 -1
- package/lib/Client.js +19 -0
- package/lib/DeviceManager.js +461 -52
- package/lib/Registration.js +10 -5
- package/lib/auth-utils.js +693 -0
- package/lib/messages/MessageSender.js +85 -24
- package/lib/noise.js +9 -4
- package/lib/signal/SignalStore.js +20 -2
- package/package.json +1 -1
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/Client.js
CHANGED
|
@@ -175,6 +175,17 @@ class WhalibmobClient extends EventEmitter {
|
|
|
175
175
|
this._signal = SignalProtocol.fromStore(this._store, signalFile, skFile);
|
|
176
176
|
this._devMgr = new DeviceManager(this);
|
|
177
177
|
|
|
178
|
+
// Restore LID ↔ phone mappings persisted from previous sessions.
|
|
179
|
+
// This ensures we can route to LID JIDs even on fresh connections where no
|
|
180
|
+
// incoming message has yet populated _pnToLid during this session.
|
|
181
|
+
const persistedLid = this._signal.store.getLidMappings
|
|
182
|
+
? this._signal.store.getLidMappings() : {};
|
|
183
|
+
for (const [phone, lid] of Object.entries(persistedLid)) {
|
|
184
|
+
this._pnToLid.set(phone, lid);
|
|
185
|
+
this._lidToPn.set(lid, phone);
|
|
186
|
+
}
|
|
187
|
+
process.stderr.write('[DBG] LID_RESTORED count=' + Object.keys(persistedLid).length + '\n');
|
|
188
|
+
|
|
178
189
|
await this._connectSocket();
|
|
179
190
|
return this;
|
|
180
191
|
}
|
|
@@ -530,6 +541,10 @@ class WhalibmobClient extends EventEmitter {
|
|
|
530
541
|
if (lidUser && pnUser && lidUser !== pnUser) {
|
|
531
542
|
this._lidToPn.set(lidUser, pnUser);
|
|
532
543
|
this._pnToLid.set(pnUser, lidUser);
|
|
544
|
+
// Persist so the mapping survives reconnects / process restarts
|
|
545
|
+
if (this._signal && this._signal.store && this._signal.store.setLidMapping) {
|
|
546
|
+
this._signal.store.setLidMapping(pnUser, lidUser);
|
|
547
|
+
}
|
|
533
548
|
}
|
|
534
549
|
}
|
|
535
550
|
process.stderr.write('[DBG] _handleMessage from=' + from + ' participant=' + participant + ' senderPn=' + senderPn + ' id=' + id + '\n');
|
|
@@ -1196,6 +1211,8 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1196
1211
|
const pnUser = pPhone.split('@')[0].split(':')[0];
|
|
1197
1212
|
this._lidToPn.set(lidUser, pnUser);
|
|
1198
1213
|
this._pnToLid.set(pnUser, lidUser);
|
|
1214
|
+
if (this._signal && this._signal.store && this._signal.store.setLidMapping)
|
|
1215
|
+
this._signal.store.setLidMapping(pnUser, lidUser);
|
|
1199
1216
|
}
|
|
1200
1217
|
}
|
|
1201
1218
|
// Case B: participant JID is a PN, lid attribute is their LID
|
|
@@ -1204,6 +1221,8 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1204
1221
|
const lidUser = pLid.split('@')[0].split(':')[0];
|
|
1205
1222
|
this._lidToPn.set(lidUser, pnUser);
|
|
1206
1223
|
this._pnToLid.set(pnUser, lidUser);
|
|
1224
|
+
if (this._signal && this._signal.store && this._signal.store.setLidMapping)
|
|
1225
|
+
this._signal.store.setLidMapping(pnUser, lidUser);
|
|
1207
1226
|
}
|
|
1208
1227
|
return { jid: pJid, role: pRole };
|
|
1209
1228
|
});
|