* 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 mentionAll support to message context for group mentions
* fix: improve readability of condition for mentions and mentionAll in generateWAMessageContent
* Apply suggestions from code review
Co-authored-by: Rajeh Taher <rajeh@reforward.dev>
---------
Co-authored-by: Rajeh Taher <rajeh@reforward.dev>
Fix typeof check: Jimp constructor is 'function' not 'object' in
jimp v1.6+. Without this fix, jpegThumbnail is never generated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes:
1. Stanza type="media" for carousel messages — WhatsApp Web needs this
to render carousel in real-time. Without it, only shows header until
page refresh (F5).
2. Fix jimp detection: typeof Jimp === 'function' (not just 'object')
so jpegThumbnail is generated for card images.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7 corrections based on reverse-engineering of WhatsApp Business Android:
1. resolveCanonicalJid: PN→LID resolution for transaction lock keys (prevent race conditions)
2. Retain PN session during LID migration (avoid No Session errors)
3. Delayed PreKey deletion with 5-min grace period (prevent Invalid PreKey ID races)
4. Surgical session cleanup: only delete the specific corrupted device, not all devices
5. Identity dual storage: save identity key in both LID and PN addresses
6. MAC error cooldown reduced 10s→1s (faster recovery, aligned with WABA)
7. Allow session recreation on first retry (retryCount >= 1 instead of > 1)
* fix: remove unused jidDecode import from decode-wa-message.ts
* fix: add try/catch to delayed PreKey deletion to prevent unhandled rejection
The setTimeout async callback in removePreKey could cause unhandled promise
rejection if the keystore was destroyed (connection closed) before the
5-min grace period expired.
The web protocol (WA\x06\x03) requires a web-compatible UserAgent.
Using SMB_ANDROID in the UserAgent caused pair code registration to
fail with "cannot connect device" even though the phone confirmation
appeared. The Android identity is now only set in DeviceProps.platformType
(ANDROID_PHONE) in the registration node, which correctly determines
the display name in "Linked Devices" as "Android (14)".
Changes:
- getUserAgent(): always returns MACOS platform and Desktop device
- getClientPayload(): always includes webInfo (no longer skipped for Android)
- Remove unused isAndroidBrowser import from validate-connection.ts
- Update pair code comments documenting tested platform IDs
- Add structured logging to pair code request
Single-line logs showing how the lib presents to WhatsApp:
- 📱/🖥️ Connection: phone number, platform, device, type
- 🔗 Pair code: companion platform, android override status
* fix: move connection log to CB:success and rename type to platformType
Move the presentation log from pre-handshake (validateConnection) to
CB:success where the connection is actually confirmed. Rename ambiguous
'type' label to 'platformType' matching DeviceProps.PlatformType.
* feat: add Android browser preset for companion device registration
Add Browsers.android(apiLevel) preset that identifies as an Android
companion device (SMB_ANDROID platform, ANDROID_PHONE device type).
Validated against Frida captures of real WhatsApp Android protocol.
* feat: support BAILEYS_BROWSER env var for browser selection
Allows selecting Android companion mode via environment variable
without code changes. Set BAILEYS_BROWSER=android (or android:15
for a specific API level) to register as an Android device.
* fix: auto-detect Android browser in pair code and fall back to Chrome
Pair code companion registration fails with Android browser because the
WA server rejects the ANDROID_PHONE companion_platform_id over the web
Noise protocol. When Android is detected, requestPairingCode() now
transparently uses Chrome/macOS platform values for the pair code stanza
while QR code pairing continues to work as Android (SMB_ANDROID).
* docs: add android to isValidBrowserPreset JSDoc
* fix: break infinite Bad MAC + session recreation loop
Remove aggressive session cleanup from the decryption hot path that caused
cascading failures when multiple messages from the same contact arrived
simultaneously. The Signal Protocol naturally recovers via retry+pkmsg flow.
Changes:
- Remove cleanupCorruptedSession() from per-message error handler (hot path)
- Move session cleanup to retry-exhausted handler as safety net
- Add per-JID retry request deduplication (5s window)
- Add 10s cooldown on MAC error session recreation
- Export cleanupCorruptedSession/getDecryptionJid for use in messages-recv
Tested: corrupted session with fake keys → Bad MAC → retry receipt →
pkmsg recovery → all 7 messages delivered, zero infinite loop.
* fix: address PR review — lint, dedup scope, and cleanup targeting
- Remove unused `autoCleanCorrupted` param from decryptMessageNode (lint fix)
- Scope retry dedup by participant JID in group chats (not group JID)
- Move dedup registration after early-return checks to avoid blocking
subsequent messages when max retries reached
- Use participant JID for session cleanup in groups (Signal sessions
are per-participant, not per-group)
* feat: centralized LID→PN normalization for all emitted events
WhatsApp's server increasingly uses LID (Linked ID) as the primary
addressing format. Consumers expect phone numbers (PN),
not opaque LID identifiers. This adds comprehensive LID→PN resolution
across all ev.emit() paths so downstream consumers always receive PN.
Key changes:
- Add resolveLidToPn() and normalizeKeyLidToPn() centralized helpers
in process-message.ts for consistent LID→PN resolution
- normalizeMessageJids() fast path: use alt JID directly from stanza
attributes (zero I/O, eliminates race condition with LIDMappingStore)
- Await storeLIDPNMappings() before normalization in message receipt
flow (was fire-and-forget, could race with subsequent getPNForLID)
- Normalize LID→PN in all event emission points:
* messages.upsert (keys, nested reaction/poll keys, participantAlt)
* presence.update (jid + participant)
* message-receipt.update (key + userJid)
* contacts.update (picture notifications)
* blocklist.update (blocklist JIDs)
* call events (chatId, from)
* group metadata (participants, owner, subjectOwner)
* group notifications (acting participant, add/remove/promote/demote)
* newsletter notifications (author, user JIDs)
* sync actions (mutation index normalization)
- Make handlePresenceUpdate and handleGroupNotification async to
support await on LID resolution
- Add normalizeGroupMetadata() helper in groups.ts for all
extractGroupMetadata call sites
Performance: ~0.01ms per message (LRU cache hit). No impact on
message sending. First-contact resolution ~2-5ms (one-time per contact).
* fix: normalize LID→PN in handleBadAck, media retry, and PDO recovery
Additional leak points found during final audit:
- handleBadAck: key.remoteJid from ack stanza could be LID
- messages.media-update: media retry key JIDs not normalized
- CTWA PDO recovery: webMessageInfo.key from phone response not normalized
* fix: address PR review — parallelize LID resolution, use helper consistently
- normalizeGroupMetadata: resolve participant LIDs with Promise.all
instead of sequential loop (perf on large groups)
- handleCall: resolve participant JIDs with Promise.all
- handleBadAck: use normalizeKeyLidToPn() helper instead of manual
resolveLidToPn + assignment (consistency with other code paths)
- CB:relay: resolve callCreator LID→PN before emitting
* fix: add timeout to fetchLatestBaileysVersion to prevent hanging connections
AbortController with 5s default timeout prevents indefinite blocking
when GitHub is unreachable (DNS failure, network issues). Falls back
to bundled version via existing catch block. Ref: Baileys#2385.
* fix: move clearTimeout to finally block in fetchLatestBaileysVersion
Ensures timer cleanup on both success and fetch rejection paths,
preventing dangling timers when DNS/network fails before abort fires.
In DMs, when the connected user sends a reaction, the inner key's
remoteJid could remain unnormalized (e.g. LID vs PN), preventing
correct matching with the original message. Also normalises participant
in groups for own messages — an improvement over upstream Baileys#2386.