refactor(api): extract the messaging domain to api/messaging.ts (R7 S3b·4)
The E2EE direct-message types (MessageUser/Message/Conversation/MyKeyBundle/ KeySetup) + the ~9 messaging methods move into a self-contained api/messaging.ts exporting a `messagingApi` slice (imports only core's req). api.ts spreads the slice and re-exports the types. api.ts 882 -> 819 lines; no runtime change. Gate green: typecheck, eslint, prettier, 74 vitest.
This commit is contained in:
+3
-66
@@ -24,6 +24,8 @@ import { downloadsApi } from "./api/downloads";
|
||||
export * from "./api/downloads";
|
||||
import { plexApi } from "./api/plex";
|
||||
export * from "./api/plex";
|
||||
import { messagingApi } from "./api/messaging";
|
||||
export * from "./api/messaging";
|
||||
|
||||
export interface Me {
|
||||
id: number;
|
||||
@@ -426,48 +428,6 @@ export interface AppNotification {
|
||||
created_at: string | null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Admin Configuration page: one DB-overridable setting (registry entry on the backend).
|
||||
export interface ConfigItem {
|
||||
key: string;
|
||||
@@ -846,30 +806,7 @@ export const api = {
|
||||
reorderViews: (ids: number[]): Promise<{ ok: boolean }> =>
|
||||
req("/api/saved-views/reorder", { method: "POST", body: JSON.stringify({ ids }) }),
|
||||
|
||||
// --- direct messages (end-to-end encrypted) ---
|
||||
messageMyKey: (): Promise<MyKeyBundle> => 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<MessageUser[]> => 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<Message> =>
|
||||
req("/api/messages", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ recipient_id: recipientId, ciphertext, iv }),
|
||||
}),
|
||||
messageUnreadCount: (): Promise<{ count: number }> => req("/api/messages/unread_count"),
|
||||
...messagingApi,
|
||||
|
||||
// --- user tags ---
|
||||
createTag: (t: { name: string; color?: string; category?: string }) =>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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<MyKeyBundle> => 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<MessageUser[]> => 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<Message> =>
|
||||
req("/api/messages", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ recipient_id: recipientId, ciphertext, iv }),
|
||||
}),
|
||||
messageUnreadCount: (): Promise<{ count: number }> => req("/api/messages/unread_count"),
|
||||
};
|
||||
Reference in New Issue
Block a user