Compare commits

...

4 Commits

Author SHA1 Message Date
Renato Alcara 9b5104b6c7 style: fix prettier formatting in viewOnceInner chain
Why: check-lint CI failed on decode-wa-message.ts:329 with prettier/prettier
error. Joining the three optional-chain operands onto one continuation line
matches printWidth=120 / useTabs config.
2026-04-26 13:35:34 -03:00
Renato Alcara 16aa02519e fix: unwrap viewOnceMessage in getMediaType so enc node gets mediatype attribute
Without this fix, view-once media (image/video/audio) was sent without
mediatype="image/video/audio" on the enc node because getMediaType only
checked the top-level message fields. The viewOnceMessage wrapper hides
the inner imageMessage, causing mediatype to be empty and WA servers to
silently drop the message on the recipient side.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:21:04 -03:00
Renato Alcara 8150587de3 fix: remove unnecessary non-null assertion on fullMessage.key
fullMessage.key is always present on WAMessage. Use consistent style
with stanza-2 handling at line 297.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:21:04 -03:00
Renato Alcara 8f1fc42cc7 fix: detect view-once media (image/video/audio) on stanza 1 for linked devices
When a view-once arrives at a linked device (InfiniteAPI), the server sends:
- Stanza 1: enc payload with full media metadata (mediaKey, directPath) wrapped in
  viewOnceMessage > imageMessage/videoMessage/audioMessage with viewOnce: true
- Stanza 2: unavailable view_once fanout placeholder (already handled)

Previously only stanza 2 set key.isViewOnce = true. Stanza 1 was emitted as
plain media with no view-once indicator, making it indistinguishable from regular
media on the consumer side (messages.upsert).

This fix inspects the decrypted proto for viewOnceMessage (v1/v2/v2Ext) wrappers
containing a media message with viewOnce=true and propagates key.isViewOnce=true.

The viewOnceMessage wrapper is also used for interactive messages (carousel,
buttons, lists) but those carry interactiveMessage/listMessage inside -- never
imageMessage.viewOnce / videoMessage.viewOnce / audioMessage.viewOnce.
The distinction is unambiguous and does not affect interactive message handling.

Verified via WA Desktop CDP capture (2026-03-19) and Android Frida DB capture
of view-once types 42/43/82 in msgstore.db (2026-03-20).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:21:04 -03:00
2 changed files with 24 additions and 0 deletions
+9
View File
@@ -1072,6 +1072,15 @@ export const makeMessagesSocket = (config: SocketConfig) => {
}
const getMediaType = (message: proto.IMessage) => {
// For view-once media, unwrap the viewOnceMessage wrapper before checking media type
const inner =
message.viewOnceMessage?.message ||
message.viewOnceMessageV2?.message ||
message.viewOnceMessageV2Extension?.message
if (inner) {
return getMediaType(inner)
}
if (message.imageMessage) {
return 'image'
} else if (message.videoMessage) {
+15
View File
@@ -319,6 +319,21 @@ export const decryptMessageNode = (
} else {
fullMessage.message = msg
}
// Detect view-once media on stanza 1 received by linked device.
// viewOnceMessage wrapper is also used for interactive messages
// (interactiveMessage, listMessage, nativeFlowMessage) -- those do NOT have
// imageMessage.viewOnce / videoMessage.viewOnce / audioMessage.viewOnce = true.
// Only real view-once media carries viewOnce: true on the inner media message.
const viewOnceInner =
msg.viewOnceMessage?.message || msg.viewOnceMessageV2?.message || msg.viewOnceMessageV2Extension?.message
if (
viewOnceInner?.imageMessage?.viewOnce ||
viewOnceInner?.videoMessage?.viewOnce ||
viewOnceInner?.audioMessage?.viewOnce
) {
fullMessage.key.isViewOnce = true
}
} catch (err: any) {
const errorContext = {
key: fullMessage.key,