feat(shared): mark_attachment_viewed + view_once on AttachmentHandle
This commit is contained in:
@@ -25,6 +25,16 @@ export interface AttachmentHandle {
|
|||||||
// base64-encoded — only readable via per-device envelope decrypt.
|
// base64-encoded — only readable via per-device envelope decrypt.
|
||||||
keyB64: string;
|
keyB64: string;
|
||||||
nonceB64: 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';
|
export type CallEventStatus = 'ended' | 'missed' | 'declined';
|
||||||
@@ -256,6 +266,7 @@ export async function insertAttachmentRow(
|
|||||||
nonce: blobNonceHex,
|
nonce: blobNonceHex,
|
||||||
mime_type: handle.mimeType,
|
mime_type: handle.mimeType,
|
||||||
size_bytes: handle.sizeBytes,
|
size_bytes: handle.sizeBytes,
|
||||||
|
view_once: handle.viewOnce ?? false,
|
||||||
};
|
};
|
||||||
if (handle.width !== undefined) row.width = handle.width;
|
if (handle.width !== undefined) row.width = handle.width;
|
||||||
if (handle.height !== undefined) row.height = handle.height;
|
if (handle.height !== undefined) row.height = handle.height;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export * from './types';
|
|||||||
export * from './userKeyMigration';
|
export * from './userKeyMigration';
|
||||||
export * from './pinnedMessages';
|
export * from './pinnedMessages';
|
||||||
export * from './mentions';
|
export * from './mentions';
|
||||||
|
export * from './viewOnceAttachments';
|
||||||
|
|
||||||
// ----- RPC wrappers ---------------------------------------------------------
|
// ----- 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),
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user