whalibmob 5.5.56 → 5.5.59

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.
Files changed (49) hide show
  1. package/README.md +526 -58
  2. package/cli.js +43 -20
  3. package/index.js +36 -6
  4. package/lib/Attestation.js +212 -0
  5. package/lib/Client.js +842 -330
  6. package/lib/DeviceManager.js +700 -182
  7. package/lib/HistorySyncHandler.js +759 -0
  8. package/lib/MediaService.js +62 -45
  9. package/lib/Registration.js +202 -184
  10. package/lib/Store.js +76 -19
  11. package/lib/WAUSync/Protocols/USyncContactProtocol.js +55 -0
  12. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +76 -0
  13. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +29 -0
  14. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +34 -0
  15. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +40 -0
  16. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +27 -0
  17. package/lib/WAUSync/Protocols/index.js +17 -0
  18. package/lib/WAUSync/USyncQuery.js +113 -0
  19. package/lib/WAUSync/USyncUser.js +40 -0
  20. package/lib/WAUSync/index.js +11 -0
  21. package/lib/auth-utils.js +693 -0
  22. package/lib/logger.js +36 -0
  23. package/lib/messages/MessageSender.js +386 -113
  24. package/lib/messages/TcTokenStore.js +116 -0
  25. package/lib/noise.js +43 -8
  26. package/lib/proto/MessageProto.js +28 -2
  27. package/lib/signal/SenderKey.js +12 -0
  28. package/lib/signal/SignalProtocol.js +70 -46
  29. package/lib/signal/SignalStore.js +20 -2
  30. package/lib/signal/WaSignalGroup/ciphertext-message.js +15 -0
  31. package/lib/signal/WaSignalGroup/group-session-builder.js +45 -0
  32. package/lib/signal/WaSignalGroup/group_cipher.js +92 -0
  33. package/lib/signal/WaSignalGroup/index.js +27 -0
  34. package/lib/signal/WaSignalGroup/keyhelper.js +24 -0
  35. package/lib/signal/WaSignalGroup/sender-chain-key.js +35 -0
  36. package/lib/signal/WaSignalGroup/sender-key-distribution-message.js +136 -0
  37. package/lib/signal/WaSignalGroup/sender-key-message.js +139 -0
  38. package/lib/signal/WaSignalGroup/sender-key-name.js +59 -0
  39. package/lib/signal/WaSignalGroup/sender-key-record.js +63 -0
  40. package/lib/signal/WaSignalGroup/sender-key-state.js +101 -0
  41. package/lib/signal/WaSignalGroup/sender-message-key.js +34 -0
  42. package/lib/signal/libsignal/curve.js +3 -1
  43. package/lib/signal/libsignal/queue_job.js +3 -1
  44. package/lib/signal/libsignal/session_builder.js +3 -1
  45. package/lib/signal/libsignal/session_cipher.js +3 -1
  46. package/lib/signal/libsignal/session_record.js +9 -9
  47. package/package.json +5 -8
  48. package/.env.example +0 -62
  49. package/lib/AttestationClient.js +0 -259
package/README.md CHANGED
@@ -3,9 +3,35 @@
3
3
 
4
4
  ##
5
5
 
6
+ > [!IMPORTANT]
7
+ > new repository whalibmob It will be maintained at the https://github.com/Kunboruto50/whalibmob.git Since I lost the Kunboruto20 account, whalibmob will be maintained and rewritten by me soon because I know that many functions in whalibmob no longer work after the WhatsApp mobile protocol change, these days I will take care of whalibmob, all good friends Fun enjoyable for all c
8
+
9
+
10
+ CONTACT ME ON TELEGRAM IF YOU WANT TO WORK WITH ME AND IF YOU HAVE PROBLEM WITH WHALIBMOB : @borutokun240
11
+
12
+ TELEGRAM NEW WHALIBMOB CHANNEL JOIN HERE https://t.me/+jWzq-I9o0Xc1Mzc8
13
+
14
+
15
+
16
+
17
+
6
18
  > [!CAUTION]
7
19
  > 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
20
 
21
+ > [!CAUTION]
22
+ > 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.
23
+
24
+
25
+ > [!IMPORTANT]
26
+ > 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"
27
+
28
+ Usdc ethereum network.
29
+
30
+
31
+
32
+
33
+
34
+
9
35
  > [!IMPORTANT]
10
36
  > 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
37
 
@@ -100,11 +126,24 @@ npm install -g whalibmob
100
126
  - [Library API](#library-api)
101
127
  - [Connecting Account](#connecting-account)
102
128
  - [Register a New Number](#register-a-new-number)
129
+ - [Device Attestation with Frida (optional)](#device-attestation-with-frida-optional)
103
130
  - [Connect](#connect)
104
131
  - [Saving & Restoring Sessions](#saving--restoring-sessions)
132
+ - [Signal Store Utilities](#signal-store-utilities)
133
+ - [makeCacheableSignalKeyStore](#makecacheablesignalkeystore)
134
+ - [addTransactionCapability](#addtransactioncapability)
135
+ - [assertMeId](#assertmeid)
136
+ - [initAuthCreds](#initauthcreds)
105
137
  - [Handling Events](#handling-events)
106
138
  - [Example to Start](#example-to-start)
107
139
  - [All Events](#all-events)
140
+ - [History Sync](#history-sync)
141
+ - [How It Works](#how-history-sync-works)
142
+ - [Listening to History Sync Events](#listening-to-history-sync-events)
143
+ - [Persistent Files Written to Disk](#persistent-files-written-to-disk)
144
+ - [Reading the History Store](#reading-the-history-store)
145
+ - [tcToken — Error 463 Defense](#tctoken--error-463-defense)
146
+ - [What Is Automatic vs What You Need to Do](#what-is-automatic-vs-what-you-need-to-do)
108
147
  - [Receiving Media](#receiving-media)
109
148
  - [Sending Messages](#sending-messages)
110
149
  - [Text Message](#text-message)
@@ -234,33 +273,112 @@ if (result.status === 'ok') {
234
273
  }
235
274
  ```
236
275
 
237
- #### Device attestation (Frida)
276
+ ### Device Attestation with Frida (optional)
238
277
 
239
- WhatsApp gates registration behind **device attestation** a Google **Play
240
- Integrity** verdict on Android and Apple **App Attest** on iOS. Those tokens can
241
- only be minted on a genuine device, which is why datacenter registrations are
242
- often refused.
278
+ WhatsApp's registration servers score every `/code` and `/register` request on how
279
+ much it looks like a genuine phone. A real device proves itself with a hardware
280
+ attestation token **Play Integrity** plus a **Keystore**-signed request on
281
+ Android, **App Attest** on iOS. whalibmob sends these fields on every registration
282
+ request, but it can only fill them with real values if it can talk to an actual
283
+ device.
243
284
 
244
- whalibmob ships the same Frida middleware [Auties00/Cobalt](https://github.com/Auties00/Cobalt)
245
- uses, under [`frida/`](frida/README.md). The scripts run on a rooted/jailbroken
246
- device and expose a local HTTP server (port `1119` = WhatsApp, `1120` = Business);
247
- whalibmob then attaches `gpia` (Play Integrity), the `H=` Keystore signature and
248
- the `Authorization` certificate chain to every `/exist`, `/code` and `/register`
249
- request — exactly like a real device.
285
+ This repository ships a **`frida/` folder** containing the on-device scripts that
286
+ produce those tokens. It is entirely **optional**: with no device attached,
287
+ whalibmob sends the same empty attestation fields a real phone sends when its
288
+ integrity check fails, and registration still works. Attaching a device raises the
289
+ trust score, which helps when you keep hitting `no_routes` or block screens.
250
290
 
251
- It is **off by default**. Enable it once the device server is reachable:
291
+ > Requires a **rooted Android phone** or a **jailbroken iPhone** with the official
292
+ > WhatsApp app installed from the Play Store / App Store. Sideloaded APKs do not
293
+ > work — the attestation is bound to the store-signed build.
294
+
295
+ #### What's in the folder
296
+
297
+ ```
298
+ frida/
299
+ android/ Play Integrity + Keystore attestation server (/integrity, /cert, /info)
300
+ ios/ App Attest attestation server (/integrity)
301
+ ```
302
+
303
+ Each platform folder has its own `README.md` with the full device prerequisites.
304
+
305
+ #### 1. Build the script
306
+
307
+ Install [Frida](https://frida.re) on your computer, then build the bundle for your
308
+ platform:
309
+
310
+ ```bash
311
+ cd frida/android # or: cd frida/ios
312
+ npm install
313
+ npm run build # produces server_with_dependencies.js
314
+ ```
315
+
316
+ #### 2. Run it on the device
317
+
318
+ Make sure the Frida server is running on the phone, then attach to WhatsApp:
319
+
320
+ ```bash
321
+ # Android — open WhatsApp and reach the "register a number" screen FIRST,
322
+ # otherwise the integrity components are not loaded yet
323
+ frida -U "WhatsApp" -l server_with_dependencies.js
324
+
325
+ # iOS
326
+ frida -U -l server_with_dependencies.js -f "net.whatsapp.WhatsApp"
327
+ ```
328
+
329
+ The script starts a small HTTP server **on the phone**, listening on port `1119`
330
+ (or `1120` if you attached to WhatsApp Business). Wait for:
331
+
332
+ ```
333
+ [*] Server ready on port 1119
334
+ ```
335
+
336
+ #### 3. Make the port reachable
337
+
338
+ whalibmob talks to that server over plain HTTP, so the port has to be reachable
339
+ from the machine running your bot. Over USB, forward it with adb:
340
+
341
+ ```bash
342
+ adb forward tcp:1119 tcp:1119 # Android
343
+ iproxy 1119 1119 # iOS (libimobiledevice)
344
+ ```
345
+
346
+ Alternatively, if the phone is on the same Wi-Fi, use its LAN IP directly.
347
+
348
+ #### 4. Point whalibmob at it
349
+
350
+ Set the host — and the port, if it isn't the default `1119`:
351
+
352
+ ```bash
353
+ WA_FRIDA_HOST=127.0.0.1
354
+ WA_FRIDA_PORT=1119 # optional; use 1120 for WhatsApp Business
355
+ ```
356
+
357
+ Or in `.env`:
252
358
 
253
- ```sh
254
- # .env
255
- WA_FRIDA_ATTESTATION=1
256
- WA_FRIDA_HOST=127.0.0.1 # e.g. after: adb forward tcp:1119 tcp:1119
257
- WA_FRIDA_PORT=1119 # 1119 WhatsApp, 1120 Business
258
- # WA_FRIDA_OPTIONAL=1 # don't fail a request if the server is down
259
359
  ```
360
+ WA_FRIDA_HOST=127.0.0.1
361
+ WA_FRIDA_PORT=1119
362
+ ```
363
+
364
+ That is the whole integration. On the next `requestSmsCode()` / `verifyCode()`
365
+ call, whalibmob queries the device automatically and attaches the attestation to
366
+ the request:
367
+
368
+ | Platform | Fields it fills |
369
+ |----------|--------------------------------------------------------------------|
370
+ | Android | `gpia` (Play Integrity) in the body, plus a Keystore signature and certificate chain on the request |
371
+ | iOS | An App Attest assertion and attestation on the request |
260
372
 
261
- See [`frida/README.md`](frida/README.md) for the full device setup. When the flag
262
- is unset, `requestSmsCode` / `verifyCode` behave exactly as before and never
263
- contact the device.
373
+ Keep the Frida session open while you register. When you unset `WA_FRIDA_HOST`,
374
+ whalibmob silently goes back to the empty-attestation path no code changes, and
375
+ nothing else in your bot is affected.
376
+
377
+ > **Troubleshooting.** If the fields come back empty, check in this order: the
378
+ > Frida session is still attached, the port is forwarded, and — on Android — that
379
+ > you opened WhatsApp's registration screen at least once before attaching, since
380
+ > the integrity components are lazy-loaded. whalibmob never fails a registration
381
+ > because attestation is unavailable; it just falls back to empty values.
264
382
 
265
383
  ### Connect
266
384
 
@@ -295,6 +413,138 @@ await client.init('919634847671')
295
413
  > [!NOTE]
296
414
  > Each phone number uses its own session file. The library handles Signal Protocol key persistence automatically.
297
415
 
416
+ ## Signal Store Utilities
417
+
418
+ `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).
419
+
420
+ ```js
421
+ const {
422
+ makeCacheableSignalKeyStore,
423
+ addTransactionCapability,
424
+ assertMeId,
425
+ initAuthCreds
426
+ } = require('whalibmob')
427
+ ```
428
+
429
+ ### `makeCacheableSignalKeyStore`
430
+
431
+ 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.
432
+
433
+ `useClones` is set to `false` so that `SessionRecord` objects — which carry internal state and methods — are returned by reference and never deep-cloned.
434
+
435
+ The wrapper also forwards `transaction()` and `isInTransaction()` calls to the underlying store when present, making it safe to stack with `addTransactionCapability`.
436
+
437
+ ```js
438
+ const { SignalStore } = require('whalibmob')
439
+ const { makeCacheableSignalKeyStore } = require('whalibmob')
440
+
441
+ const store = new SignalStore(/* ... */)
442
+ const cached = makeCacheableSignalKeyStore(store)
443
+
444
+ // reads hit cache after first access
445
+ const session = await cached.getSession('919634847671@s.whatsapp.net:0')
446
+ ```
447
+
448
+ **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.
449
+
450
+ ### `addTransactionCapability`
451
+
452
+ 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.
453
+
454
+ Uses `AsyncLocalStorage` to propagate transaction context across async call chains, and a per-key-type `Mutex` with reference-counting to serialize concurrent writers safely.
455
+
456
+ ```js
457
+ const { addTransactionCapability, makeCacheableSignalKeyStore } = require('whalibmob')
458
+
459
+ // recommended: cache first, then transactions on top
460
+ const base = new SignalStore(/* ... */)
461
+ const cached = makeCacheableSignalKeyStore(base)
462
+ const txnStore = addTransactionCapability(cached)
463
+
464
+ // inside a send flow
465
+ await txnStore.transaction(async () => {
466
+ // all writes are buffered
467
+ await txnStore.setSession('919634847671@s.whatsapp.net:0', sessionRecord)
468
+ await txnStore.setPreKey(1, preKeyPair)
469
+ // commit is called automatically at the end of the transaction callback
470
+ })
471
+ ```
472
+
473
+ Stacking order matters: put `makeCacheableSignalKeyStore` below `addTransactionCapability` so that the cache always sees the committed state.
474
+
475
+ **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.
476
+
477
+ ### `assertMeId`
478
+
479
+ 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`.
480
+
481
+ ```js
482
+ const { assertMeId } = require('whalibmob')
483
+
484
+ const store = loadStore(sessFile)
485
+
486
+ try {
487
+ const jid = assertMeId(store)
488
+ // jid === '919634847671@s.whatsapp.net'
489
+ console.log('account JID:', jid)
490
+ } catch (err) {
491
+ console.error('store is not registered:', err.message)
492
+ }
493
+ ```
494
+
495
+ **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.
496
+
497
+ ### `initAuthCreds`
498
+
499
+ 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`.
500
+
501
+ ```js
502
+ const { initAuthCreds, saveStore } = require('whalibmob')
503
+ const path = require('path')
504
+ const fs = require('fs')
505
+
506
+ const phone = '919634847671'
507
+ const sessDir = path.join(process.env.HOME, '.waSession')
508
+ const sessFile = path.join(sessDir, phone + '.json')
509
+
510
+ fs.mkdirSync(sessDir, { recursive: true })
511
+
512
+ const store = initAuthCreds(phone)
513
+ saveStore(store, sessFile)
514
+ ```
515
+
516
+ This is the function used internally by the CLI for all new session creation. Prefer it over `createNewStore` for forward compatibility.
517
+
518
+ > [!NOTE]
519
+ > `initAuthCreds` and `createNewStore` produce equivalent stores for all current library operations. The additional fields from `initAuthCreds` are there for future-proofing and interoperability.
520
+
521
+ ### Recommended Stacking Pattern
522
+
523
+ For a production multi-account server:
524
+
525
+ ```js
526
+ const {
527
+ SignalStore,
528
+ makeCacheableSignalKeyStore,
529
+ addTransactionCapability,
530
+ initAuthCreds,
531
+ saveStore,
532
+ loadStore
533
+ } = require('whalibmob')
534
+
535
+ // 1. load or create the credential store
536
+ let store = loadStore(sessFile) || initAuthCreds(phone)
537
+
538
+ // 2. build the layered Signal key store
539
+ const signalStore = new SignalStore(store)
540
+ const cachedStore = makeCacheableSignalKeyStore(signalStore)
541
+ const txnStore = addTransactionCapability(cachedStore)
542
+
543
+ // 3. pass to the client (advanced usage — most users should use WhalibmobClient directly)
544
+ ```
545
+
546
+ For standard usage, `WhalibmobClient` handles all of this internally. These helpers are for advanced scenarios where you need direct control over Signal key storage.
547
+
298
548
  ## Handling Events
299
549
 
300
550
  whalibmob uses the EventEmitter syntax for events.
@@ -421,6 +671,261 @@ The `decoded` object shape per message type:
421
671
  { type: 'protocol', subtype: string }
422
672
  ```
423
673
 
674
+ ---
675
+
676
+ ## History Sync
677
+
678
+ ### How History Sync Works
679
+
680
+ When whalibmob connects, WhatsApp automatically sends the account's chat history to the client.
681
+ The library handles the entire pipeline **without any code from you** — you only need to listen
682
+ to the events if you want to use the data.
683
+
684
+ The full internal flow:
685
+
686
+ 1. WhatsApp server sends an encrypted `ProtocolMessage` (type 6) containing a `HistorySyncNotification`.
687
+ 2. The library decrypts it via Signal Protocol.
688
+ 3. `HistorySyncHandler` downloads the encrypted blob from WhatsApp's CDN (`mmg.whatsapp.net`), or reads the inline payload if the server embedded it directly.
689
+ 4. The blob is decrypted with **AES-256-CBC** using an HKDF key derived from `"WhatsApp History Keys"`.
690
+ 5. The result is decompressed with **zlib** and decoded from **protobuf** (WAProto v2.3000.x — field numbers verified against the official proto definition).
691
+ 6. Chats, contacts, push names, LID↔PN mappings, and **tcTokens** are merged into the disk store.
692
+ 7. tcTokens from history are seeded into `TcTokenStore` in memory so the first outbound DM after reconnect already carries a valid `<tctoken>` node (prevents error 463 on cold start).
693
+ 8. The `history_sync` event fires with a summary of what was received.
694
+
695
+ History arrives in **multiple chunks**. Each chunk fires one `history_sync` event. The first chunk (sync type `INITIAL_BOOTSTRAP`) is usually the largest and carries the most recent conversations.
696
+
697
+ ### Listening to History Sync Events
698
+
699
+ ```js
700
+ const { WhalibmobClient } = require('whalibmob')
701
+ const path = require('path')
702
+
703
+ const client = new WhalibmobClient({
704
+ sessionDir: path.join(process.env.HOME, '.waSession')
705
+ })
706
+
707
+ // history_sync fires once per history chunk — may fire multiple times on first connect
708
+ client.on('history_sync', result => {
709
+ console.log('History sync chunk received:')
710
+ console.log(' type :', result.syncTypeName) // e.g. 'INITIAL_BOOTSTRAP', 'RECENT', 'FULL'
711
+ console.log(' progress:', result.progress) // 0-100 server-reported
712
+ console.log(' chunk :', result.chunkOrder)
713
+ console.log(' chats :', result.chats.length)
714
+ console.log(' contacts:', result.contacts.length)
715
+ console.log(' pushNames:', (result.pushNames || []).length)
716
+ })
717
+
718
+ // history_sync_error fires if a chunk fails to download or decrypt
719
+ client.on('history_sync_error', ({ err, notification }) => {
720
+ console.error('History sync failed:', err.message)
721
+ console.error(' syncType:', notification.syncType)
722
+ })
723
+
724
+ await client.init('919634847671')
725
+ ```
726
+
727
+ The `result` object shape emitted by `history_sync`:
728
+
729
+ ```js
730
+ {
731
+ syncType: number, // HistorySyncType enum value
732
+ syncTypeName: string, // 'INITIAL_BOOTSTRAP' | 'RECENT' | 'FULL' | 'PUSH_NAME' | 'NON_BLOCKING_DATA' | 'ON_DEMAND'
733
+ progress: number, // 0-100, server-reported progress
734
+ chunkOrder: number, // chunk sequence number
735
+ chats: [ // one entry per conversation in this chunk
736
+ {
737
+ id: string, // chat JID e.g. '919634847671@s.whatsapp.net' or '120363...@g.us'
738
+ name: string, // display name (may be undefined for unknown contacts)
739
+ unreadCount: number,
740
+ lastMsgTimestamp: number, // Unix seconds
741
+ messageCount: number // number of messages in this chunk for this chat
742
+ }
743
+ ],
744
+ contacts: [ // one entry per contact discovered in this chunk
745
+ {
746
+ id: string,
747
+ name: string,
748
+ username: string, // WhatsApp username if set
749
+ pnJid: string, // phone-number JID e.g. '919634847671@s.whatsapp.net'
750
+ lidJid: string // LID JID e.g. '112345678901234@lid'
751
+ }
752
+ ],
753
+ pushNames: Array, // push-name entries { id, pushname }
754
+ lidPnMappings: Array, // LID<->PN mapping entries { lidJid, pnJid }
755
+ merged: object // raw merged history store (see Reading the History Store below)
756
+ }
757
+ ```
758
+
759
+ Sync type values:
760
+
761
+ | `syncTypeName` | When it fires |
762
+ |---|---|
763
+ | `INITIAL_BOOTSTRAP` | First connect — most recent conversations |
764
+ | `RECENT` | Reconnect after a short offline period |
765
+ | `FULL` | Full historical sync (older messages) |
766
+ | `PUSH_NAME` | Contact name updates only |
767
+ | `NON_BLOCKING_DATA` | Background low-priority data |
768
+ | `ON_DEMAND` | Explicitly requested by the client |
769
+
770
+ ### Persistent Files Written to Disk
771
+
772
+ The library automatically writes these files to `sessionDir` per account. You do not need to create or manage them.
773
+
774
+ | File | Contents |
775
+ |---|---|
776
+ | `<phone>.history.json` | Chats, contacts, push names, LID↔PN mappings, tcTokens |
777
+ | `<phone>.messages.json` | Flat map of `msgId → message metadata` |
778
+ | `<phone>.appStateKeys.json` | App-state sync keys (used for app-state patch decryption) |
779
+ | `<phone>.tctoken.json` | Trusted-contact token store (tcToken per contact JID) |
780
+
781
+ ### Reading the History Store
782
+
783
+ After history sync completes you can read the on-disk files directly:
784
+
785
+ ```js
786
+ const fs = require('fs')
787
+ const path = require('path')
788
+
789
+ const sessDir = path.join(process.env.HOME, '.waSession')
790
+ const phone = '919634847671'
791
+
792
+ // ── Read chats ────────────────────────────────────────────────────────────────
793
+ const histPath = path.join(sessDir, phone + '.history.json')
794
+ const hist = JSON.parse(fs.readFileSync(histPath, 'utf8'))
795
+
796
+ // List all chats sorted by last message time
797
+ const chats = Object.values(hist.chats)
798
+ .sort((a, b) => (b.lastMsgTimestamp || 0) - (a.lastMsgTimestamp || 0))
799
+
800
+ for (const chat of chats.slice(0, 10)) {
801
+ console.log(chat.id, '|', chat.name || '(unknown)', '|', chat.unreadCount, 'unread')
802
+ }
803
+
804
+ // ── LID ↔ PN lookup ───────────────────────────────────────────────────────────
805
+ // Look up LID JID from phone number JID
806
+ const myLid = hist.pnLidMap['919634847671@s.whatsapp.net']
807
+ console.log('LID:', myLid) // e.g. '112345678901234@lid'
808
+
809
+ // Reverse: phone number JID from LID
810
+ const myPn = hist.lidPnMap[myLid]
811
+ console.log('PN:', myPn)
812
+
813
+ // ── Read message metadata ─────────────────────────────────────────────────────
814
+ const msgPath = path.join(sessDir, phone + '.messages.json')
815
+ const msgs = JSON.parse(fs.readFileSync(msgPath, 'utf8'))
816
+ const msgList = Object.values(msgs).sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))
817
+ console.log('Total messages indexed:', msgList.length)
818
+ console.log('Latest:', msgList[0])
819
+ ```
820
+
821
+ `hist.chats` schema per chat entry:
822
+
823
+ ```js
824
+ {
825
+ id: string, // JID
826
+ name: string,
827
+ displayName: string,
828
+ unreadCount: number,
829
+ lastMsgTimestamp: number, // Unix seconds
830
+ messageCount: number, // total messages indexed from history
831
+ ephemeralExpiry: number, // disappearing messages timer in seconds, if set
832
+ archived: boolean,
833
+ pinned: number, // pin sort order (0 = not pinned)
834
+ tcToken: string, // base64 — trusted-contact token (used internally by the library)
835
+ tcTokenTimestamp: number, // Unix seconds — when the token was issued by the server
836
+ tcTokenSenderTimestamp: number // Unix seconds — sender-side issuance timestamp for 7-day bucket dedup
837
+ }
838
+ ```
839
+
840
+ `msgs` schema per message entry:
841
+
842
+ ```js
843
+ {
844
+ id: string, // WhatsApp message ID
845
+ chatId: string, // JID of the conversation
846
+ fromMe: boolean,
847
+ fromJid: string, // sender JID
848
+ timestamp: number, // Unix seconds
849
+ pushName: string, // display name of sender at send time
850
+ status: number // 0=error 1=pending 2=server 3=delivered 4=read 5=played
851
+ }
852
+ ```
853
+
854
+ ### tcToken — Error 463 Defense
855
+
856
+ > [!IMPORTANT]
857
+ > This section is informational. The entire tcToken lifecycle is **fully automatic**.
858
+ > You do not need to write any code for it.
859
+
860
+ WhatsApp counts every outbound DM sent **without** a `<tctoken>` node as an anonymous "reach-out" event. Once enough such events accumulate the server enforces a time-based **Reach-out Time-lock** and returns error `463` (`NackCallerReachoutTimelocked`), blocking all outbound messages and calls for a period.
861
+
862
+ whalibmob implements the full lifecycle to prevent this:
863
+
864
+ | Step | What the library does automatically |
865
+ |---|---|
866
+ | **History seed** | On every history sync chunk, `tcToken` bytes are extracted from each conversation in the protobuf and loaded into `TcTokenStore` in memory. The first send after reconnect already has a valid token ready — no 463 risk on cold start. |
867
+ | **Attach on send** | Before dispatching any DM, `MessageSender` looks up the token for the recipient JID, checks it has not expired (28-day rolling window), and pushes a `<tctoken>` child node into the message stanza. |
868
+ | **Proactive issuance** | After each successful DM send, the library fires a `<iq type='set' xmlns='privacy'>` requesting a fresh token for that JID from the server — once per 7-day bucket, deduplicated in-flight. |
869
+ | **Incoming notification** | When a contact starts a new conversation, WhatsApp pushes a `<notification type='privacy_token'>`. The library catches it in `_handlePrivacyTokenNotification` and stores the token immediately. |
870
+ | **Identity change re-issue** | When decrypting a `pkmsg` (new Signal session from peer), the library calls `_reissueTcTokenAfterIdentityChange` to re-issue the token for the new session. |
871
+ | **Error 463 recovery** | If a send fails with error 463, the library issues a fresh token, waits for the server response, and automatically retries the same message with the new token attached. |
872
+ | **Expiry** | Tokens use a 4-bucket rolling window (4 × 7 days = 28-day TTL). Expired tokens are cleared before the send and a fresh one is requested proactively. |
873
+
874
+ Token storage uses the **LID JID** of the contact (e.g. `112345678901234@lid`) as the key — never the phone-number JID — matching WhatsApp's internal convention.
875
+
876
+ ### What Is Automatic vs What You Need to Do
877
+
878
+ **Everything in the "Automatic" column requires zero code from you.**
879
+
880
+ | Feature | Automatic | Notes |
881
+ |---|---|---|
882
+ | Download history blob from CDN | ✅ | |
883
+ | Decrypt history blob (AES-256-CBC + HKDF) | ✅ | |
884
+ | Decompress zlib | ✅ | |
885
+ | Decode protobuf (WAProto v2.3000.x) | ✅ | Field numbers verified against official proto |
886
+ | Persist chats / contacts / push names | ✅ | Written to `<phone>.history.json` |
887
+ | Persist message metadata | ✅ | Written to `<phone>.messages.json` |
888
+ | Persist app-state sync keys | ✅ | Written to `<phone>.appStateKeys.json` |
889
+ | Seed tcTokens into memory on connect | ✅ | Prevents error 463 on first send after reconnect |
890
+ | Attach tcToken to every outbound DM | ✅ | |
891
+ | Issue fresh tcTokens after each send | ✅ | Once per 7-day bucket per contact |
892
+ | Handle incoming `privacy_token` notifications | ✅ | |
893
+ | Re-issue tcToken after peer identity change | ✅ | |
894
+ | Recover from error 463 with automatic retry | ✅ | |
895
+ | Populate in-memory LID↔PN maps | ✅ | |
896
+ | Listen to `history_sync` event | 🔵 Optional | Only if your app needs to react to history data |
897
+ | Read `<phone>.history.json` | 🔵 Optional | Only if your app needs chat/contact data at rest |
898
+ | Read `<phone>.messages.json` | 🔵 Optional | Only if your app indexes messages |
899
+
900
+ **Minimum working integration — history sync, tcTokens, and error-463 defense all active with zero extra code:**
901
+
902
+ ```js
903
+ const { WhalibmobClient } = require('whalibmob')
904
+ const path = require('path')
905
+
906
+ const client = new WhalibmobClient({
907
+ sessionDir: path.join(process.env.HOME, '.waSession')
908
+ })
909
+
910
+ // History sync, tcToken seeding, error-463 defense, and all disk persistence
911
+ // happen automatically. Add the listeners below only if your app needs the data.
912
+
913
+ client.on('history_sync', result => {
914
+ // Optional — fires once per chunk (multiple times on first connect)
915
+ console.log('[', result.syncTypeName, ']',
916
+ result.chats.length, 'chats,',
917
+ result.contacts.length, 'contacts')
918
+ })
919
+
920
+ client.on('history_sync_error', ({ err }) => {
921
+ // Optional — log failures (library continues working even if a chunk fails)
922
+ console.error('History chunk failed:', err.message)
923
+ })
924
+
925
+ await client.init('919634847671')
926
+ ```
927
+
928
+
424
929
  ## Receiving Media
425
930
 
426
931
  When a media message arrives, `msg.decoded` contains a CDN `url` and a `mediaKey`.
@@ -2317,43 +2822,6 @@ These variables override individual fields on top of the selected profile:
2317
2822
  | `WA_VERSION` | Pin the WhatsApp version (e.g. `2.24.13.80`). Skips the live store fetch. |
2318
2823
  | `WA_STATIC_TOKEN` | Override the static token used in registration token computation. |
2319
2824
 
2320
- ### SIM & Locale Overrides
2321
-
2322
- During registration the library derives `sim_mcc`, `sim_mnc`, `lg` and `lc` from the
2323
- country code of the number being registered, so the request looks like a SIM from
2324
- that country. Override them when the guessed operator is wrong for your number
2325
- (MVNOs, ported numbers, VoIP ranges):
2326
-
2327
- | Variable | Description |
2328
- |---|---|
2329
- | `WA_SIM_MCC` | Mobile Country Code (e.g. `262` for Germany) |
2330
- | `WA_SIM_MNC` | Mobile Network Code (e.g. `01` for Telekom) |
2331
- | `WA_LG` | Language tag, ISO 639-1 (e.g. `de`) |
2332
- | `WA_LC` | Country tag, ISO 3166-1 alpha-2 (e.g. `DE`) |
2333
- | `WA_PUSH_TOKEN` | APNS/FCM push token to include on the `/code` request. Only sent when set. |
2334
- | `WA_PUSH_CODE` | Silent-push verification code to include on the `/code` request. Only sent when set. |
2335
-
2336
- ### Proxy (SOCKS5 / Tor)
2337
-
2338
- WhatsApp routinely refuses registration from datacenter and VPS IPs — that is the
2339
- most common cause of `reason: "no_routes"` and of security blocks. Route
2340
- registration traffic through a residential or mobile proxy:
2341
-
2342
- ```sh
2343
- npm install socks
2344
- export TOR_PROXY=socks5://127.0.0.1:9050
2345
- # with credentials:
2346
- export TOR_PROXY=socks5://user:pass@proxy.example.com:1080
2347
- ```
2348
-
2349
- | Variable | Description |
2350
- |---|---|
2351
- | `TOR_PROXY` | SOCKS4/SOCKS5 proxy URL for registration traffic. Supports `user:pass@`. |
2352
- | `SOCKS_PROXY` | Alias for `TOR_PROXY`. |
2353
-
2354
- `socks` is an optional dependency: it is only required when one of these variables
2355
- is set, so installs without a proxy are unaffected.
2356
-
2357
2825
  ## License
2358
2826
 
2359
2827
  MIT