feat(shared): mark_attachment_viewed + view_once on AttachmentHandle

This commit is contained in:
byGalax
2026-05-16 18:12:15 +02:00
parent 526f9d7bcc
commit 915569db39
3 changed files with 41 additions and 0 deletions
+11
View File
@@ -25,6 +25,16 @@ export interface AttachmentHandle {
// base64-encoded — only readable via per-device envelope decrypt.
keyB64: string;
nonceB64: string;
/** View-once flag — set by sender on the encrypted envelope and mirrored
* to `message_attachments.view_once` so the renderer can show a "tap to
* view" tombstone and call the mark-viewed RPC on first open. */
viewOnce?: boolean;
/** ISO timestamp the first non-sender opened the attachment. Populated by
* the mark-viewed RPC (server-authoritative); undefined until burnt. */
viewedAt?: string | null;
/** UUID of the first non-sender who viewed. Populated alongside `viewedAt`
* by the mark-viewed RPC so the renderer can render attribution. */
viewedBy?: string | null;
}
export type CallEventStatus = 'ended' | 'missed' | 'declined';
@@ -256,6 +266,7 @@ export async function insertAttachmentRow(
nonce: blobNonceHex,
mime_type: handle.mimeType,
size_bytes: handle.sizeBytes,
view_once: handle.viewOnce ?? false,
};
if (handle.width !== undefined) row.width = handle.width;
if (handle.height !== undefined) row.height = handle.height;
+1
View File
@@ -9,6 +9,7 @@ export * from './types';
export * from './userKeyMigration';
export * from './pinnedMessages';
export * from './mentions';
export * from './viewOnceAttachments';
// ----- RPC wrappers ---------------------------------------------------------
@@ -0,0 +1,29 @@
import type { AppSupabaseClient } from '../supabase/client';
export interface ViewOnceResult {
viewOnce: boolean;
/** ISO timestamp set the moment the first non-sender viewed. */
viewedAt?: string | null;
/** True iff the caller is the original sender (they don't burn the view). */
self?: boolean;
/** True iff someone else already viewed before this call. */
already?: boolean;
}
export async function markAttachmentViewed(
client: AppSupabaseClient,
attachmentId: string,
): Promise<ViewOnceResult> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { data, error } = await (client as any).rpc('mark_attachment_viewed', {
p_attachment_id: attachmentId,
});
if (error) throw error;
const d = (data ?? {}) as Record<string, unknown>;
return {
viewOnce: Boolean(d.view_once),
viewedAt: typeof d.viewed_at === 'string' ? d.viewed_at : null,
self: Boolean(d.self),
already: Boolean(d.already),
};
}