fix: address codex/copilot review on view-once send/recv
P1 (messages-recv.ts): remove early return for <unavailable type="view_once"/>. decryptMessageNode already handles this gracefully — decode-wa-message.ts sets fullMessage.key.isViewOnce=true and skips the CIPHERTEXT stub (line 464). The early return was preventing upsertMessage from emitting an event to consumers, making view-once sends from the primary phone invisible on linked devices. P2 (messages-send.ts): restrict isViewOnceMsg detection to actual view-once media. Previously checked only for the presence of viewOnceMessage* wrappers, which are also used by buttons/lists/carousels (non-view-once). Now unwraps the inner message and checks imageMessage.viewOnce / videoMessage.viewOnce / audioMessage.viewOnce, consistent with the detection in decode-wa-message.ts. Prevents companion DSM filtering from incorrectly applying to interactive messages. P3 (messages-send.ts): move assertSessions to use only the recipients actually encrypted for. For view-once, omitted companions no longer cause assertSessions to fail or do unnecessary session validation work. Co-Authored-By: Renato Alcara
This commit is contained in:
@@ -2258,17 +2258,6 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle view-once unavailable sync from primary device.
|
||||
// When the primary device sends a view-once, linked companions receive
|
||||
// <unavailable type="view_once"/> — no enc content, just a sync signal.
|
||||
// Acknowledge and skip decryption (nothing to decrypt).
|
||||
const unavailableNode = getBinaryNodeChild(node, 'unavailable')
|
||||
if (unavailableNode?.attrs?.type === 'view_once') {
|
||||
logger.debug({ id: node.attrs.id, from: node.attrs.from }, 'received view_once unavailable sync from primary device')
|
||||
await sendMessageAck(node)
|
||||
return
|
||||
}
|
||||
|
||||
const {
|
||||
fullMessage: msg,
|
||||
category,
|
||||
|
||||
@@ -1277,21 +1277,32 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
: otherRecipients
|
||||
const effectiveAllRecipients = [...effectiveMeRecipients, ...effectiveOtherRecipients]
|
||||
|
||||
await assertSessions(effectiveAllRecipients)
|
||||
// P2: detect actual view-once media by checking inner message's viewOnce flag.
|
||||
// viewOnceMessage wrapper is also used for interactive messages (buttons, lists, etc)
|
||||
// which do NOT carry viewOnce=true on the media — those must NOT be filtered.
|
||||
const viewOnceInner =
|
||||
message.viewOnceMessageV2?.message ||
|
||||
message.viewOnceMessage?.message ||
|
||||
message.viewOnceMessageV2Extension?.message
|
||||
const isViewOnceMsg = !!(
|
||||
viewOnceInner?.imageMessage?.viewOnce ||
|
||||
viewOnceInner?.videoMessage?.viewOnce ||
|
||||
viewOnceInner?.audioMessage?.viewOnce
|
||||
)
|
||||
|
||||
// For view-once: only send DSM to primary phone (device=0).
|
||||
// Companion devices (device>0) are omitted — WA server generates
|
||||
// <unavailable type="view_once"/> for them automatically.
|
||||
// Sending explicit <unavailable> from a companion is rejected by the server.
|
||||
const isViewOnceMsg = !!(
|
||||
message.viewOnceMessageV2 ||
|
||||
message.viewOnceMessage ||
|
||||
message.viewOnceMessageV2Extension
|
||||
)
|
||||
const viewOnceMeRecipients = isViewOnceMsg
|
||||
? effectiveMeRecipients.filter(jid => !jidDecode(jid)?.device)
|
||||
: effectiveMeRecipients
|
||||
|
||||
// P3: assert sessions only for recipients we actually encrypt for.
|
||||
// For view-once, companions are omitted — asserting their sessions is wasteful
|
||||
// and could block the send if a companion session is corrupted.
|
||||
await assertSessions([...viewOnceMeRecipients, ...effectiveOtherRecipients])
|
||||
|
||||
const [
|
||||
{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 },
|
||||
{ nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }
|
||||
|
||||
Reference in New Issue
Block a user