// Direct-messaging domain: the E2EE key bundle setup/reset, the conversation list, threads, and // send/unread. Self-contained — types reference only each other, methods only touch core's req. // (The WebCrypto layer that encrypts/decrypts message bodies lives in e2ee.ts, not here.) import { req } from "./core"; export interface MessageUser { id: number; name: string; avatar_url: string | null; } export interface Message { id: number; kind: "user" | "system"; sender_id: number | null; recipient_id: number; body: string | null; // plaintext, system messages only ciphertext: string | null; // base64, E2EE user messages only iv: string | null; read_at: string | null; created_at: string | null; } export interface Conversation { partner: MessageUser; last_message: Message; unread: number; } // My own E2EE key bundle. When configured, the wrapped blob + params let a device unlock. export interface MyKeyBundle { configured: boolean; public_key?: string; wrapped_private_key?: string; salt?: string; wrap_iv?: string; key_check?: string; } export interface KeySetup { public_key: string; wrapped_private_key: string; salt: string; wrap_iv: string; key_check: string; } export const messagingApi = { messageMyKey: (): Promise => req("/api/messages/keys/me"), setupMessageKey: (k: KeySetup): Promise<{ configured: boolean }> => req("/api/messages/keys", { method: "POST", body: JSON.stringify(k) }), // Forgotten-passphrase reset: drops the server key (and the now-undecryptable history) so a new // passphrase can be set. Password accounts must pass their current password; errors shown inline. resetMessageKey: (currentPassword?: string): Promise<{ configured: boolean }> => req( "/api/messages/keys/reset", { method: "POST", body: JSON.stringify({ current_password: currentPassword ?? null }) }, { quiet: true }, ), messagePublicKey: (userId: number): Promise<{ user_id: number; public_key: string }> => req(`/api/messages/keys/${userId}`), messageUsers: (): Promise => req("/api/messages/users"), conversations: (): Promise<{ items: Conversation[] }> => req("/api/messages/conversations"), thread: (partnerId: number): Promise<{ partner: MessageUser; items: Message[] }> => req(`/api/messages/conversations/${partnerId}`), sendMessage: (recipientId: number, ciphertext: string, iv: string): Promise => req("/api/messages", { method: "POST", body: JSON.stringify({ recipient_id: recipientId, ciphertext, iv }), }), messageUnreadCount: (): Promise<{ count: number }> => req("/api/messages/unread_count"), };