diff --git a/src/Socket/messages-recv.ts b/src/Socket/messages-recv.ts
index 9318a1b3..58848a05 100644
--- a/src/Socket/messages-recv.ts
+++ b/src/Socket/messages-recv.ts
@@ -2258,6 +2258,17 @@ 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
+ // — 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,
diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts
index 211b6280..d1ec3132 100644
--- a/src/Socket/messages-send.ts
+++ b/src/Socket/messages-send.ts
@@ -1279,19 +1279,35 @@ export const makeMessagesSocket = (config: SocketConfig) => {
await assertSessions(effectiveAllRecipients)
+ // For view-once: only send DSM to primary phone (device=0).
+ // Companion devices (device>0) are omitted — WA server generates
+ // for them automatically.
+ // Sending explicit 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
+
const [
{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 },
{ nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }
] = await Promise.all([
// For own devices: use DSM (deviceSentMessage) wrapper
- createParticipantNodes(effectiveMeRecipients, meMsg || message, extraAttrs),
+ createParticipantNodes(viewOnceMeRecipients, meMsg || message, extraAttrs),
createParticipantNodes(effectiveOtherRecipients, message, extraAttrs)
])
participants.push(...meNodes)
participants.push(...otherNodes)
- if (effectiveMeRecipients.length > 0 || effectiveOtherRecipients.length > 0) {
- extraAttrs['phash'] = generateParticipantHashV2(effectiveAllRecipients)
+ const phashRecipients = isViewOnceMsg
+ ? [...viewOnceMeRecipients, ...effectiveOtherRecipients]
+ : effectiveAllRecipients
+ if (phashRecipients.length > 0) {
+ extraAttrs['phash'] = generateParticipantHashV2(phashRecipients)
}
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2