feat(desktop): view-once image attachments (sender toggle + recipient lightbox + tombstone)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
byGalax
2026-05-16 18:18:39 +02:00
parent 915569db39
commit 95156a65eb
5 changed files with 157 additions and 5 deletions
@@ -70,7 +70,12 @@ function rowToMessage(row: Record<string, unknown>): MessageWithCipher {
}
export function useConversationMessages({ conversationId, userId, deviceId }: Args): State & {
send: (text: string, images?: File[], replyToId?: string | null) => Promise<void>;
send: (
text: string,
images?: File[],
replyToId?: string | null,
opts?: { viewOnce?: boolean },
) => Promise<void>;
refresh: () => Promise<void>;
pending: OutboxItem[];
retryPending: (id: string) => void;
@@ -559,7 +564,12 @@ export function useConversationMessages({ conversationId, userId, deviceId }: Ar
);
const send = useCallback(
async (text: string, images: File[] = [], replyToId: string | null = null) => {
async (
text: string,
images: File[] = [],
replyToId: string | null = null,
opts: { viewOnce?: boolean } = {},
) => {
const trimmed = text.trim();
if ((!trimmed && images.length === 0) || !conversationId || !userId || !deviceId) return;
const priv = privateKeyRef.current;
@@ -631,6 +641,14 @@ export function useConversationMessages({ conversationId, userId, deviceId }: Ar
...(dims.width !== undefined ? { width: dims.width } : {}),
...(dims.height !== undefined ? { height: dims.height } : {}),
});
// Stamp the view-once flag on each handle the caller requested it
// for. The flag rides inside the encrypted payload (so peers can
// render the locked card without leaking who-sent-what to the
// server) AND lands on the public message_attachments row via
// insertAttachmentRow below (where the mark-viewed RPC enforces it).
if (opts.viewOnce) {
res.handle.viewOnce = true;
}
handles.push(res.handle);
blobNonceHexByHandleId.set(res.handle.id, bytesToPgHex(res.nonce));
}