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";
|
export * from "./api/downloads";
|
||||||
import { plexApi } from "./api/plex";
|
import { plexApi } from "./api/plex";
|
||||||
export * from "./api/plex";
|
export * from "./api/plex";
|
||||||
|
import { messagingApi } from "./api/messaging";
|
||||||
|
export * from "./api/messaging";
|
||||||
|
|
||||||
export interface Me {
|
export interface Me {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -426,48 +428,6 @@ export interface AppNotification {
|
|||||||
created_at: string | null;
|
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).
|
// Admin Configuration page: one DB-overridable setting (registry entry on the backend).
|
||||||
export interface ConfigItem {
|
export interface ConfigItem {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -846,30 +806,7 @@ export const api = {
|
|||||||
reorderViews: (ids: number[]): Promise<{ ok: boolean }> =>
|
reorderViews: (ids: number[]): Promise<{ ok: boolean }> =>
|
||||||
req("/api/saved-views/reorder", { method: "POST", body: JSON.stringify({ ids }) }),
|
req("/api/saved-views/reorder", { method: "POST", body: JSON.stringify({ ids }) }),
|
||||||
|
|
||||||
// --- direct messages (end-to-end encrypted) ---
|
...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"),
|
|
||||||
|
|
||||||
// --- user tags ---
|
// --- user tags ---
|
||||||
createTag: (t: { name: string; color?: string; category?: string }) =>
|
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