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 unified session handling and time constants
* refactor: improve socket variable destructuring and presence update logic
* fix: remove unnecessary semicolons in socket and time constants definitions
* fix: handle invalid server time offset parsing in makeSocket function
* fix(proto-extract): regenerate corrupted yarn.lock to restore install process
* chore(proto-extract): update acorn parser to latest version for compatibility with new WhatsApp JS syntax
* Update baileys version to 2.3000.1029027441
* Update version number in Defaults index
* Revert WAProto.proto to resolve merge conflict and restore expected structure
---------
Co-authored-by: Vrypt <vryptt@gmail.com>
Co-authored-by: Rajeh Taher <rajeh@reforward.dev>
* fix: ensure proper socket closure and await connection termination in tests
* feat(tests): enhance E2E tests for image and video message handling, including downloads and group interactions
* feat: add patch-tojson functionality for improved proto serialization
* Remove patch-tojson functionality and its import from the main index file to streamline the codebase.
* refactor: simplify longToString and longToNumber functions for better readability and performance