Commit Graph

2981 Commits

Author SHA1 Message Date
Renato Alcara 35461f96c5 fix(inbound): align mutex key with processMessage chat scope
Addresses CodeRabbit Minor + Copilot review on PR #392:

1. Replace `msg.key.remoteJid || msg.key.id || 'unknown'` with
   `getChatId(msg.key) + jidNormalizedUser`. The previous derivation:
   - Diverged from processMessage's chat scoping (which calls
     `jidNormalizedUser(getChatId(message.key))`). For non-status
     broadcasts processMessage targets `participant`, not `remoteJid`,
     so the inner mutex would queue under a different key than the chat
     processMessage actually mutates — defeating per-chat ordering for
     broadcast traffic.
   - Used `msg.key.id` as a fallback. Per-message ids are unique, so
     malformed-key messages each got their own queue, defeating the
     mutex entirely. Replaced with the constant `'unknown'` bucket.

2. Drop hard-coded line numbers (`process-message.ts:650`,
   `messages-recv.ts:2416`) from comments — references rot. Comments
   now point at the function/handler instead (`APP_STATE_SYNC_KEY_SHARE
   handler`, `inbound caller`).
2026-04-26 14:38:40 -03:00
Renato Alcara 1e37448e05 fix(inbound): avoid messageMutex deadlock in appStateSyncKeyShare branch
Addresses Copilot review on PR #392.

The previous commit wrapped postUpsertWork in messageMutex.mutex(chatId, ...)
for both branches and then awaited it in the appStateSyncKeyShare path. This
deadlocks when upsertMessage is invoked from messages-recv.ts:2416, which is
already holding messageMutex.mutex(chatId, ...) for the same chat:

  outer mutex (in caller)  → await upsertMessage(...)
   └─ upsertMessage work() → schedules inner mutex on same chatId (queued)
       └─ await postUpsertWork  ← inner cannot acquire while outer holds it
                                   outer cannot release while it awaits inner
                                   = eternal deadlock (async-mutex is not re-entrant)

Fire-and-forget path is safe because it does NOT await: outer releases as
soon as upsertMessage returns, then the queued inner acquires.

Fix: in the appStateSyncKeyShare branch, run postUpsertTasks() inline (no
inner messageMutex). Per-chat ordering is still preserved by the caller's
outer mutex while we await. Other paths keep the inner mutex wrapper for
the latency win + per-chat ordering of detached work.
2026-04-26 14:14:16 -03:00
Renato Alcara 72e78a12e0 fix(inbound): preserve per-chat ordering + key-share await in detached upsert
Addresses bot review on PR #392:

1. Codex P1 + CodeRabbit Major: removing the `await` on Promise.all broke
   the per-chat ordering guarantee enforced by `messageMutex` in
   messages-recv.ts:2416 (`await messageMutex.mutex(... await upsertMessage(...))`).
   The outer mutex unlocked before the detached processMessage finished,
   so a follow-up message from the same chat could overtake state mutations
   (chat.unreadCount, LID/PN mapping, messages.update emits, history downloads).

   Fix: wrap the fire-and-forget Promise.all in `messageMutex.mutex(chatId, ...)`
   inside chats.ts. The outer mutex (in messages-recv) releases as soon as
   upsertMessage returns (fast — work is detached), then this inner mutex
   enqueues per-chat. Reentrancy is safe because outer always releases before
   inner acquires.

2. CodeRabbit Critical: appStateSyncKeyShare race. processMessage persists
   the new app-state-sync key via keyStore.transaction at process-message.ts:650.
   Previously the awaited Promise.all guaranteed the key was in the store
   before the follow-up `await doAppStateSync()` (which decodes patches via
   that key) ran. After detaching, doAppStateSync could hit isMissingKeyError
   and park collections in blockedCollections, requiring an extra retry cycle.

   Fix: for the rare appStateSyncKeyShare + Syncing branch, await the
   postUpsertWork before triggering doAppStateSync. All other messages stay
   fire-and-forget — the latency win is preserved for the hot path.

3. Copilot nit: enrich the warn payload with messageId, remoteJid and
   shouldProcessHistoryMsg so background failures can be correlated.

4. CodeRabbit nit: replace the IIFE around the conditional doAppStateSync
   with a ternary expression.
2026-04-26 13:47:14 -03:00
Renato Alcara a5b7ce7ef9 perf(inbound): detach post-upsert work from buffered function
Removes the await around Promise.all([doAppStateSync, processMessage])
inside upsertMessage. createBufferedFunction (event-buffer.ts:819) only
schedules the buffer flush AFTER its work() callback resolves, so awaiting
post-upsert work pinned the messages.upsert emit inside the buffer for the
full duration of processMessage (signal cleanup, store writes, tcToken
maintenance, history mappings).

After this change the buffered function returns as soon as the synchronous
state-machine bookkeeping completes, the debounced flush releases
messages.upsert to the consumer, and processMessage / doAppStateSync run
in the background. Failures are caught and warn-logged so unhandled
rejections cannot crash the socket.

This is the last remaining sync hop on the inbound hot path that was still
adding latency on top of PR #390 (fire-and-forget LID mapping + tcToken
history sync).
2026-04-26 13:21:52 -03:00
Renato Alcara df1acc8f0c chore(logs): reduce decrypt-error noise (~75% fewer lines per recover… (#391)
chore(logs): reduce decrypt-error noise (~75% fewer lines per recover… (#391)
2026-04-26 11:36:21 -03:00
Renato Alcara 1dffe3b311 perf(inbound-latency): restore async LID mapping + fire-and-forget tc… (#390)
* perf(inbound-latency): restore async LID mapping + fire-and-forget tctoken history sync

PRODUCTION ISSUE: Inbound messages from smartphone to ZPRO frontend were
arriving with seconds of delay. Outbound (ZPRO → smartphone) was instant.
Started after PR #386 (tctoken lifecycle) deploy.

ROOT CAUSE: Three compounding factors:

1. The historical fix d73cd28d39 (2026-02-03, "fix inbound latency by making
   LID mapping async") was partially reverted the same day by c3fc792351
   ("hybrid approach") due to a valid race-condition concern with decrypt().
   The reversion was over-protective: storeLIDPNMappings does NOT need to be
   sync — only migrateSession does. The hybrid kept all 3 awaits sync.

2. PR #386 added `await storeTcTokensFromHistorySync(...)` BEFORE the
   `messaging-history.set` emit. Per chunk this drains the event buffer with
   2-4 store ops, which compounds when many chunks arrive at once (restart,
   QR scan, multi-device login).

3. Each pre-check `await getPNForLID(alt)` / `getLIDForPN(alt)` before
   storeLIDPNMappings was redundant — the store has its own LRU cache + dedup.

Combined under production load (multi-instance store contention, post-PR #386
extra ops per send) the per-message hot-path penalty became user-visible delay.

THIS FIX:

#1+#3: messages-recv.ts ~line 2332 — `storeLIDPNMappings` becomes
fire-and-forget, pre-check `getPNForLID/getLIDForPN` removed. `migrateSession`
stays SYNC (REQUIRED for decrypt — see Codex/Copilot review on PR #72 / commit
c3fc792351). normalizeMessageJids has a fast-path that uses key.*Alt directly
without hitting the store, so the just-arrived message normalizes correctly
even before the background store completes.

#2: process-message.ts ~line 451 — `storeTcTokensFromHistorySync` becomes
fire-and-forget. Trade-off: a listener firing an outbound send IMMEDIATELY
after the emit may race the background persistence and hit error 463 on that
specific send. Existing 463 handler in messages-recv.ts triggers
getPrivacyTokens() refetch that auto-recovers in seconds. Net UX is much
better than per-chunk stalls.

INVARIANTS PRESERVED:
- migrateSession remains SYNC — decrypt() depends on it (race condition guard)
- normalizeMessageJids remains SYNC — events need correct JIDs before emit
- messageMutex remains SYNC — per-chat ordering preserved
- All 824 tests still pass
2026-04-26 10:30:09 -03:00
Renato Alcara 4fe708445a chore: update WhatsApp Web version to v2.3000.1038167900 (#389)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-26 06:18:52 -03:00
github-actions[bot] 336ed64a44 chore: update proto/version to v2.3000.1038164556 (#388)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-26 01:07:43 -03:00
Renato Alcara 65cb09e4df perf(history-sync): parallelise tcToken JID resolution per chunk (#387)
perf(history-sync): parallelise tcToken JID resolution per chunk (#387)
2026-04-25 23:52:37 -03:00
Renato Alcara 9ff21db749 feat(tctoken): complete lifecycle (TIER 1 + 2 + 3 of upstream PR)
feat(tctoken): complete lifecycle (TIER 1 + 2 + 3 of upstream PR)
2026-04-25 18:52:45 -03:00
Renato Alcara 9525f42cb2 perf: optimize history sync memory and CPU usage (#385)
* perf: optimize history sync memory and CPU usage

Aligns with Baileys upstream PR #2333 . Surgical port of
4 micro-optimizations + 2 test files. No collision with InfiniteAPI's
LID/PN, carousel, buttons, Bad MAC or app state sync resilience code.

Changes:

1. WAProto longToString/longToNumber: use native BigInt instead of
   Long library division loops. ~4.6x faster per call (237ns -> 52ns).
   BigInt.toString() delegates to V8 native C++. Same change applied
   to fix-imports.js (template) and index.js (generated artifact) so
   future regenerations stay in sync.

2. downloadHistory: stream-pipe createInflate instead of buffering
   the full compressed payload then inflating. Cuts RSS peak ~50%
   on 50MB history payloads.

3. processHistoryMessage: drop the { ...chat } shallow copy before
   pushing to chats[]. The decoded protobuf object is not referenced
   after the push.

4. downloadEncryptedContent transform: skip Buffer.concat when
   remainingBytes is empty (the common case — chunks usually arrive
   AES-aligned). ~19x faster per chunk (78ms -> 4ms over 25k chunks).

Tests:
- bigint-validation.test.ts (NEW): 32 parameterized cases covering
  zero, MAX_SAFE_INTEGER boundary, max int64/uint64, negative values,
  tricky bit patterns and powers of 2; plus 200-iter fuzz comparing
  old Long vs new BigInt implementations for equivalence.
- proto-tojson-long.test.ts: kept the existing fork-only test for
  string-in-Long-field graceful handling, appended 4 upstream tests
  covering Long->string fast path, large unsigned > MAX_SAFE_INTEGER,
  zero/small values and encode/decode roundtrip.
2026-04-25 15:11:34 -03:00
Renato Alcara e117456079 test(binary): cover reconnection sync skip for both signals (#384)
* test(binary): cover reconnection sync skip for both signals

Ports the upstream reconnection-sync-skip test from Baileys PR #2350
and adapts it to cover InfiniteAPI's dual-signal reconnect detection
in chats.ts:

1. accountSyncCounter > 0 (a previous full sync completed)
2. socketSkippedOfflineBuffer (forwarded from socket.ts when
   hadStaleRoutingInfo is true, e.g. routingInfo discarded on start)

Both are required because they cover different reconnect scenarios
and the absence of the second branch causes a buffer mismatch where
socket.ts skips the offline buffer while chats.ts still waits in
AwaitingInitialSync — stalling live messages for up to 4 seconds.

Co-Authored-By: Renato Alcara
2026-04-25 14:16:14 -03:00
Renato Alcara c179c82cca perf(messages-recv): early-ignore JIDs before buffer/queue (#383)
* perf(messages-recv): early-ignore JIDs before buffer/queue

Aligns with Baileys upstream PR #2352. Moves the shouldIgnoreJid check
out of handleMessage/handleReceipt/handleNotification and into
processNode so ignored stanzas are acked and dropped before entering
ev.buffer(), the offline queue, or the keyed mutexes. Skips the LID->PN
resolution work in handleReceipt for ignored receipts as a side effect.

Diverges from upstream by excluding type='call' from the early-ignore
check — InfiniteAPI's handleCall has never used shouldIgnoreJid, so
keeping calls outside this filter preserves existing behavior for
integrators that filter messages but still want call events.

Carousel, buttons, LID/PN normalization, Bad MAC fix and retry manager
untouched.

Co-Authored-By: Renato Alcara
2026-04-25 13:16:22 -03:00
Renato Alcara d08a09c35f feat: add inbound username support and USync username protocol (#382)
* feat: add inbound username support and USync username protocol

Aligns with Baileys upstream PR #2480. WhatsApp is rolling out an
inbound username field that maps to the user's LID. This change is
purely additive — it captures and propagates the new optional field
through types, decoders, USync queries, and group/contact events.

Skipped intentionally to preserve InfiniteAPI's LID/PN customization:
- handleGroupNotification in messages-recv.ts (custom LID->PN flow on
  groups.upsert / participants.map). Username for these specific
  events still flows via process-message's emitParticipantsUpdate
  (reads message.key.participantUsername added in decode-wa-message).

Carousel/buttons code (messages.ts, messages-send.ts) untouched.

Co-Authored-By: Renato Alcara
2026-04-25 12:39:31 -03:00
Renato Alcara cffaad9e5a fix(chats): use abt xmlns and protocol
fix(chats): use abt xmlns and protocol
2026-04-25 11:08:47 -03:00
Renato Alcara 104d2c47f5 chore: update WhatsApp Web version to v2.3000.1038147544 (#380)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-25 06:17:10 -03:00
Renato Alcara 924c54f1f8 chore: update WhatsApp Web version to v2.3000.1038063777 (#378)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-24 06:31:31 -03:00
github-actions[bot] e34ef5479d chore: update proto/version to v2.3000.1038024963 (#377)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-24 01:03:36 -03:00
Renato Alcara 5e8d30d709 chore: update WhatsApp Web version to v2.3000.1037968143 (#376)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-23 06:31:41 -03:00
github-actions[bot] ac93080595 chore: update proto/version to v2.3000.1037953056 (#375)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-23 01:01:23 -03:00
Renato Alcara 37d1bdfb76 chore: update WhatsApp Web version to v2.3000.1037878879 (#374)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-22 06:29:59 -03:00
github-actions[bot] 76b2007f27 chore: update proto/version to v2.3000.1037847281 (#373)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-22 01:00:13 -03:00
Renato Alcara 676912c295 Fix/remove vo raw debug logs (#372)
* chore: remove temporary [VO_RAW] debug logs from handleMessage

* chore: remove temporary VO_TRACE debug logger.info from handleMessage
2026-04-21 15:09:00 -03:00
Renato Alcara 06d9f35ca9 chore: remove temporary [VO_RAW] debug logs from handleMessage (#371)
chore: remove temporary [VO_RAW] debug logs from handleMessage (#371)
2026-04-21 15:00:00 -03:00
Renato Alcara 4cadcd1c57 chore: update WhatsApp Web version to v2.3000.1037786138 (#370)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-21 06:30:57 -03:00
github-actions[bot] 937b26edcd chore: update proto/version to v2.3000.1037753511 (#368)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-21 01:01:09 -03:00
Renato Alcara acad0c06dc chore: update WhatsApp Web version to v2.3000.1037679412 (#369)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-20 06:33:26 -03:00
Renato Alcara 98995aae89 chore: update WhatsApp Web version to v2.3000.1037659536 (#367)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-19 06:15:22 -03:00
github-actions[bot] 7a7d92e3f8 chore: update proto/version to v2.3000.1037654574 (#366)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-19 01:01:54 -03:00
Renato Alcara 1fbaa284f9 chore: update WhatsApp Web version to v2.3000.1037639827 (#365)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-18 06:15:03 -03:00
github-actions[bot] 9c7dd0460c chore: update proto/version to v2.3000.1037622359 (#364)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-18 00:55:26 -03:00
Renato Alcara 579277cb29 chore: update WhatsApp Web version to v2.3000.1037554587 (#363)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-17 06:29:43 -03:00
github-actions[bot] bb0cb90260 chore: update proto/version to v2.3000.1037539916 (#358)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-17 01:01:19 -03:00
Renato Alcara 56955e12f5 chore: update WhatsApp Web version to v2.3000.1037471135 (#362)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-16 06:29:30 -03:00
Renato Alcara b7d9f2b866 chore: update WhatsApp Web version to v2.3000.1037376462 (#361)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-15 06:30:13 -03:00
Renato Alcara daaf0bc813 chore: update WhatsApp Web version to v2.3000.1037280436 (#360)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-14 06:29:53 -03:00
Renato Alcara bf95867089 Merge branch 'master' of https://github.com/rsalcara/InfiniteAPI 2026-04-13 07:20:06 -03:00
Renato Alcara 9fac156474 feat: add dual protocol support (web/native) via BAILEYS_PROTOCOL env
Adds experimental native protocol mode (WAM\x05) as alternative to
web protocol (WA\x06\x03). Configurable via BAILEYS_PROTOCOL env var:
- web (default): standard WhatsApp Web protocol
- native: Android native protocol (may enable view-once media on companions)

When native mode is enabled:
- NOISE_WA_HEADER changes from WA\x06\x03 to WAM\x05
- DICT_VERSION changes from 3 to 5
- WebInfo is omitted from ClientPayload

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:19:55 -03:00
Renato Alcara 230e08037b chore: update WhatsApp Web version to v2.3000.1037186195 (#359)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-13 06:33:07 -03:00
Renato Alcara 77ffe59911 Revert "feat: add WebdPayload with E2E media support and features bitmask"
This reverts commit 313f304ddf.
2026-04-13 00:01:32 -03:00
Renato Alcara 313f304ddf feat: add WebdPayload with E2E media support and features bitmask
Add webdPayload to WebInfo during connection with:
- supportsE2EImage/Video/Audio/Document = true
- supportsMediaRetry = true
- features bitmask with view-once capability flag

This may enable the WA server to deliver view-once media content
to Baileys companions instead of just <unavailable> placeholders.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:43:17 -03:00
Renato Alcara a74fbb08a6 fix: request view-once content via PDO from primary phone
When receiving <unavailable type='view_once'/>, attempt to request
the actual media content from the primary phone via PDO
(Peer Data Operation / PLACEHOLDER_MESSAGE_RESEND).

If the phone responds, the full media (url, mediaKey, directPath)
arrives via messages.upsert, allowing consumers to display the image.

Falls back to placeholder if PDO fails or phone doesn't respond.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:21:02 -03:00
Renato Alcara 0e6bad72bb fix: emit view-once placeholder from unavailable handler
The early unavailable handler at line ~2261 was intercepting
view_once stanzas and silently discarding them (sendMessageAck + return)
before the view_once_unavailable_fanout handler could emit them.

Now emits a viewOnceMessage placeholder with isViewOnce=true so
consumers receive the event in messages.upsert.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:14:13 -03:00
Renato Alcara 08c19e2951 merge: resolve conflict in messages-recv.ts (keep viewOnceMessage placeholder) 2026-04-12 11:55:12 -03:00
Renato Alcara 90061dd6a1 fix: allow msmsg decryption and emit view-once unavailable fanout (#355)
Two issues found during view-once testing on linked devices:

1. msmsg block removal:
   The "temporary fix" that discarded all msmsg-type encrypted messages
   was also blocking legitimate view-once messages. On linked devices,
   view-once arrives as msmsg encryption type. The block was originally
   added to prevent crashes from missing messageSecret, but the existing
   try/catch in the decrypt path already handles those errors gracefully
   by marking the message as a CIPHERTEXT stub.

2. view_once_unavailable_fanout emission:
   When a linked device receives a view-once, the WA server sends only
   <unavailable type="view_once"/> (no <enc> with actual media content).
   Previously this was silently acknowledged without emitting any event,
   so consumers (zpro, astra-api, etc.) never knew a view-once arrived.
   Now it emits the message as a CIPHERTEXT stub with key.isViewOnce=true
   so consumers can display "view-once message received" in their UI.

Co-Authored-By: Renato Alcara
2026-04-12 11:46:42 -03:00
Renato Alcara d014a2496e fix: allow msmsg decryption and emit view-once unavailable fanout (#355)
Two issues found during view-once testing on linked devices:

1. msmsg block removal:
   The "temporary fix" that discarded all msmsg-type encrypted messages
   was also blocking legitimate view-once messages. On linked devices,
   view-once arrives as msmsg encryption type. The block was originally
   added to prevent crashes from missing messageSecret, but the existing
   try/catch in the decrypt path already handles those errors gracefully
   by marking the message as a CIPHERTEXT stub.

2. view_once_unavailable_fanout emission:
   When a linked device receives a view-once, the WA server sends only
   <unavailable type="view_once"/> (no <enc> with actual media content).
   Previously this was silently acknowledged without emitting any event,
   so consumers (zpro, astra-api, etc.) never knew a view-once arrived.
   Now it emits the message as a CIPHERTEXT stub with key.isViewOnce=true
   so consumers can display "view-once message received" in their UI.

Co-Authored-By: Renato Alcara
2026-04-12 10:02:30 -03:00
Renato Alcara 47b0940428 chore: update WhatsApp Web version to v2.3000.1037166441 (#357)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-12 06:15:16 -03:00
github-actions[bot] 90937af374 chore: update proto/version to v2.3000.1037162330 (#356)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-12 01:00:42 -03:00
Renato Alcara 703b5da554 chore: update WhatsApp Web version to v2.3000.1037137890 (#354)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-11 06:13:10 -03:00
github-actions[bot] 8c61ce5f8e chore: update proto/version to v2.3000.1037082622 (#353)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-04-11 00:48:54 -03:00