Adds `Browsers.android(apiLevel)` preset that registers the companion
device as Android in WhatsApp's "Linked Devices" list, enabling
view-once messages and Android-specific features.
Inspired by PR #2201 (WhiskeySockets/Baileys#2201) which introduced
the concept, but with critical fixes discovered through extensive
reverse engineering and real-device testing.
## Usage
const sock = makeWASocket({
browser: Browsers.android('14'),
auth: state
})
The device appears as "Android (14)" in the phone's Linked Devices list.
## Investigation and Findings
### Two different platform fields
WhatsApp uses two separate platform identifiers that serve different
purposes:
1. `companion_platform_id` (pair code stanza) — validated by the server
BEFORE showing the confirmation prompt on the phone. Controls whether
the server accepts the pairing request.
2. `DeviceProps.PlatformType` (registration node) — sent AFTER pairing
confirmation. Determines the display name in "Linked Devices".
These are independent: you can pair with Chrome (1) as companion but
register with ANDROID_PHONE (16) in DeviceProps, and the device will
correctly appear as "Android" in linked devices.
### Pair code platform testing results
Tested with real WhatsApp server via web protocol (WA\x06\x03):
| companion_platform_id | Result |
|-----------------------|-------------------------------------------|
| Chrome (1) | WORKS — phone shows "connect device?" prompt |
| ANDROID_PHONE (16) | TIMEOUT — server silently ignores stanza |
| UWP (21) | REJECTED — "cannot connect device" error |
When we sent `companion_platform_id: "16"` (ANDROID_PHONE), the server
did not respond at all — the request timed out after 30s with no
`link_code_pairing_ref` response. With Chrome ("1"), it responds
immediately.
This confirms: ANDROID_PHONE(16) is a primary device type, not a
companion type. The server expects companions with this type to use
the native Android protocol (WAM\x05 with key_attestation TEE
certificates). Since Baileys uses the web protocol (WA\x06\x03),
the server rejects it.
### UserAgent.Platform testing results
The original PR #2201 used `UserAgent.Platform = SMB_ANDROID` for
Android browser. Testing revealed this breaks pair code registration:
- With `SMB_ANDROID`: pair code generates, phone shows confirmation,
user clicks "Yes", but registration FAILS with "cannot connect
device" error. The mismatch between web protocol handshake
(WA\x06\x03) and Android UserAgent causes the server to reject
the registration.
- With `MACOS`: pair code works end-to-end. Device connects
successfully and appears as "Android (14)" in linked devices.
Additionally, the upstream default `UserAgent.Platform = WEB` causes
405 connection failures on some server endpoints. Using `MACOS` (24)
resolves this.
### WhatsApp Desktop Windows investigation (Frida + binary analysis)
To understand how official WhatsApp clients handle platform identity,
we reverse-engineered WhatsApp Desktop for Windows:
- WhatsApp Desktop is a WebView2 wrapper loading
`web.whatsapp.com?windows=1` — NOT a native client
- Uses the same web protocol (WA\x06\x03) as Chrome
- Registers with `DeviceProps.PlatformType = UWP (21)` to appear as
"Windows" in linked devices
- Always shows as "Windows" regardless of QR or pair code connection
This confirmed our approach: the display name comes from DeviceProps,
not from the pair code stanza.
### Relevant protocol enums
ClientPayload.UserAgent.Platform (connection identity):
MACOS = 24 (what we use — web-compatible)
SMB_ANDROID = 10 (breaks pair code — DO NOT USE)
WEB = 15 (causes 405 errors)
DeviceProps.PlatformType (linked device display name):
CHROME = 1, FIREFOX = 2, SAFARI = 5, EDGE = 6
DESKTOP = 7, CATALINA = 12, ANDROID_PHONE = 16, UWP = 21
## Changes
- Types/index.ts: add `android(apiLevel)` to BrowsersMap type
- browser-utils.ts: add `Browsers.android()` preset,
`isAndroidBrowser()` helper, fix `getPlatformId()` to resolve
ANDROID -> ANDROID_PHONE (16) since the enum only has
ANDROID_PHONE, not ANDROID
- validate-connection.ts: use MACOS platform in UserAgent (fixes 405
and pair code), map ANDROID -> ANDROID_PHONE in getPlatformType()
- socket.ts: auto-detect Android browser in pair code and fall back
to Chrome (1) as companion_platform_id. Non-Android browsers are
unaffected and continue using their native platform ID.
## How it works
With `Browsers.android('14')`:
- `getUserAgent()` -> platform: MACOS, device: Desktop (web identity)
- `getClientPayload()` -> includes webInfo as normal
- `getPlatformType('Android')` -> ANDROID_PHONE (in DeviceProps)
- Pair code -> companion_platform_id: '1' (Chrome fallback)
- QR code -> no override needed, works directly
- Result: device appears as "Android (14)" in Linked Devices
* feat(signal): add RetryReason enum and MAC error-based session recreation
* feat(signal): add identity change detection with automatic session clearing
* fix(signal): integrate identity change detection with pkmsg decryption
This completes the identity change detection implementation by actually
calling saveIdentity() during pkmsg decryption, which is CRITICAL for
the feature to work.
Changes:
- Add extractIdentityFromPkmsg() function that parses PreKeyWhisperMessage
protobuf to extract sender's identity key (33 bytes)
- Call saveIdentity() BEFORE decryption in decryptMessage() for pkmsg type
- Log when identity change is detected
Flow:
1. Receive pkmsg from sender
2. Extract identity key from PreKeyWhisperMessage protobuf
3. Call storage.saveIdentity() which compares with stored key
4. If key changed → session is cleared atomically
5. Decryption proceeds with re-established session
This matches WhatsApp Web's behavior where extractIdentityKey is called
before handleNewSession (GysEGRAXCvh.js:40917, 48815).
Ref: WhatsApp Web's extractIdentityKey (GysEGRAXCvh.js:48976-48998)
* feat: replace async crypto with sync Rust WASM for app state sync
* fix: remove unecessary buffer copying
* fix: update whatsapp-rust-bridge to version 0.5.2 and refactor async calls to sync. HKDF and MD5 in rust
* fix: improve message resend logic by adding checks for message IDs
* Revert "fix: improve message resend logic by adding checks for message IDs"
This reverts commit c03f9d8e6fc6cbfbb9d1f8f67c169700e704213d.
* feat: add group member label update functionality and event emission
* feat: refactor updateMemberLabel function for improved readability
* feat: use optional chaining for label association message in processMessage
* feat: add updateMemberLabel to makeMessagesSocket for enhanced functionality
* fix: correct log message for group member tag update event
Co-authored-by: FgsiDev
* fix: handle invalid signatureKeyPublic types in sender-key-state
- added extra check to ensure signatureKeyPublic is either a base64 string or a Buffer
- fallback to empty buffer when unexpected object type is received
- prevents ERR_INVALID_ARG_TYPE error in Buffer.from
* adjusted based on @jlucaso1 recommmendation
* fix: handle chain key objects for skmsg group message decryption
previously, some sender chain keys were stored or retrieved as plain objects
(e.g. { '0': 85, '1': 100, ... }) instead of Buffers. this caused
"ERR_INVALID_ARG_TYPE" during initial decryption of skmsg group messages.
this fixes any chain key object is converted to a proper Buffer
before being passed to SenderChainKey.
* fix: add more robust checks and fallbacks
* feat: async cache
feat: async caching
* fix: linting issues
* fix: mget logic
---------
Co-authored-by: αѕтяσχ11 <devastro0010@gmail.com>
* lid-mapping: get missing lid from usync
* lid-mapping, jid-utils: change to isPnUser and store multiple mappings
* process-message: parse protocolMsg mapping, and store from new msgs
* types: lid-mapping event, addressing enum, alt, contact, group types
* validate, decode: use lid for identity, better logic
* lid: final commit
* linting
* linting
* linting
* linting
* misc: fix testing and also remove version json
* lint: IDE fucking up lint
* lid-mapping: fix build error on NPM
* message-retry: fix proto import
* mutex reimplementation
* lint
* lint
* lint fix
* Add cleanup for expired sender key mutexes
Introduces a mechanism to periodically clean up unused sender key mutexes in addTransactionCapability, reducing memory usage by removing mutexes that have not been used for over an hour and are not locked. A timer is started when the first mutex is created, and cleanup runs every 30 minutes.
* Update auth-utils.ts
* Refactor Signal key transaction usage
Introduces a local variable for parsed Signal keys in libsignal.ts to avoid repeated type assertions and improve code clarity.
* Refactor transaction handling for key operations
Introduces per-entity mutexes and passes key identifiers to transaction calls for finer-grained concurrency control in Signal key storage and socket operations. Updates transaction signatures and usage across Signal, Socket, and Utils modules to improve atomicity and performance, especially for system and sync messages.
* Improve SignalKeyStore transaction handling
Refactored transaction logic in SignalKeyStore to add commit retries, cleanup of transaction state, and improved mutex management for sender keys. Enhanced validation and batching for pre-key deletions and updates, improving concurrency and reliability.
* Replace custom mutex cleanup with LRU cache
Switched from manual mutex cleanup logic to using the lru-cache package for managing mutexes in addTransactionCapability. This simplifies resource management and ensures expired mutexes are automatically purged. Added lru-cache as a dependency.
* Lint fix
* Update src/Signal/libsignal.ts
* Update libsignal.ts
---------
Co-authored-by: João Lucas <jlucaso@hotmail.com>
Co-authored-by: Rajeh Taher <rajeh@reforward.dev>
* Add message retry manager for session recreation and caching
Introduces a MessageRetryManager to cache recent sent messages and manage automatic Signal session recreation for failed message retries. Adds new config options to enable these features, integrates retry manager into message send/receive logic, and provides periodic cache cleanup. Improves reliability of message delivery and retry handling.
* Add buffer timeout and cache limit to event-buffer
Introduces a buffer timeout to auto-flush events after 30 seconds and limits the history cache size to prevent memory bloat in event-buffer.ts.
* Add session validation to SignalRepository
Introduces a validateSession method to SignalRepository for checking session existence and state. Updates message retry logic to use the new validation, improving session recreation handling and retry management.
* Improve message retry management and tracking
Refactored message retry manager to use LRU caches for recent messages and session history, added retry counters and statistics tracking, and implemented phone request scheduling. Updated message receive and send logic to mark retry success and pass max retry count. Removed periodic cleanup logic in favor of cache TTLs for better resource management.
* lint fix
* Use validateSession for session checks in message send
Replaces direct session existence checks with the improved validateSession method, which also checks for session staleness. Adds debug logging for failed session validations to aid troubleshooting.
* Delete src/Utils/message-retry.ts
---------
Co-authored-by: Rajeh Taher <rajeh@reforward.dev>
* feat: implement state machine for chat synchronization and buffer management
* test: Add Jest configuration and tests for connection deadlock handling
- Created a Jest configuration file to set up testing environment.
- Implemented utility functions for creating isolated authentication sessions and mocking WebSocket connections.
- Added a test case to ensure that the connection does not deadlock when history sync is disabled, verifying the correct handling of message events.
* feat: add GitHub Actions workflow for running tests
* chore: sort import lint
* fix: implement timeout handling for AwaitingInitialSync state in chat socket, maybe fix memory leak
* feat: enhance chat synchronization by refining AwaitingInitialSync handling and adding history sync checks
* feat: implement basic newsletter functionality with socket integration and event handling
* feat: enhance media handling for newsletters with raw media upload support
* feat: working updatePicture, removePicure, adminCount, mute, Unmute
* fix: fetchMessages
* chore: cleanup
* fix: update newsletter metadata path and query ID for consistency. newsletterMetadata works now
* chore: enhance newsletter metadata parsing and error handling
* fix: correct DELETE QueryId value in Newsletter.ts
* chore: split mex stuffs to own file
* chore: remove as any