import { getDesktopServerUrl } from "./desktopServerConfig"; import { isTauriRuntime } from "./platform"; const SAVED_LOGIN_CREDENTIAL_VERSION = 1; export interface SavedLoginCredential { email: string; password: string; } type StoredLoginCredential = { version?: unknown; email?: unknown; password?: unknown; savedAt?: unknown; }; const invokeLoginCredential = async ( command: "login_credential_get" | "login_credential_set" | "login_credential_delete", args: Record, ): Promise => { const { invoke } = await import("@tauri-apps/api/core"); return invoke(command, args); }; const normalizeEmail = (email: string): string => email.trim().toLowerCase(); const serializeDesktopCredential = (credential: SavedLoginCredential): string => JSON.stringify({ version: SAVED_LOGIN_CREDENTIAL_VERSION, email: normalizeEmail(credential.email), password: credential.password, savedAt: Date.now(), }); const parseDesktopCredential = (stored: string | null): SavedLoginCredential | null => { if (!stored) return null; try { const payload = JSON.parse(stored) as StoredLoginCredential; if ( payload.version !== SAVED_LOGIN_CREDENTIAL_VERSION || typeof payload.email !== "string" || typeof payload.password !== "string" || typeof payload.savedAt !== "number" ) { return null; } const email = normalizeEmail(payload.email); if (!email || !payload.password) return null; return { email, password: payload.password }; } catch { return null; } }; const getDesktopSavedLoginCredential = async (): Promise => { const serverOrigin = getDesktopServerUrl(); if (!serverOrigin) return null; const stored = await invokeLoginCredential("login_credential_get", { serverOrigin }); const parsed = parseDesktopCredential(stored); if (!parsed && stored) { await invokeLoginCredential("login_credential_delete", { serverOrigin }); } return parsed; }; const saveDesktopLoginCredential = async (credential: SavedLoginCredential): Promise => { const serverOrigin = getDesktopServerUrl(); if (!serverOrigin) return false; await invokeLoginCredential("login_credential_set", { serverOrigin, credential: serializeDesktopCredential(credential), }); return true; }; const clearDesktopLoginCredential = async (): Promise => { const serverOrigin = getDesktopServerUrl(); if (!serverOrigin) return false; await invokeLoginCredential("login_credential_delete", { serverOrigin }); return true; }; const getPasswordCredentialConstructor = (): (new (data: { id: string; name?: string; password: string }) => unknown) | null => { const ctor = (window as unknown as { PasswordCredential?: unknown }).PasswordCredential; return typeof ctor === "function" ? (ctor as new (data: { id: string; name?: string; password: string }) => unknown) : null; }; const getBrowserCredentialContainer = (): | { get?: (options: Record) => Promise; store?: (credential: unknown) => Promise; preventSilentAccess?: () => Promise; } | null => { const container = (navigator as unknown as { credentials?: unknown }).credentials; return container && typeof container === "object" ? (container as any) : null; }; const getBrowserSavedLoginCredential = async (): Promise => { const credentials = getBrowserCredentialContainer(); if (!credentials?.get) return null; try { const credential = await credentials.get({ password: true, mediation: "optional", }); const passwordCredential = credential as { id?: unknown; password?: unknown } | null; if (typeof passwordCredential?.id !== "string" || typeof passwordCredential.password !== "string") { return null; } const email = normalizeEmail(passwordCredential.id); if (!email || !passwordCredential.password) return null; return { email, password: passwordCredential.password }; } catch { return null; } }; const saveBrowserLoginCredential = async (credential: SavedLoginCredential): Promise => { const credentials = getBrowserCredentialContainer(); const PasswordCredential = getPasswordCredentialConstructor(); if (!credentials?.store || !PasswordCredential) return false; const email = normalizeEmail(credential.email); if (!email || !credential.password) return false; await credentials.store( new PasswordCredential({ id: email, name: email, password: credential.password, }), ); return true; }; const clearBrowserLoginCredential = async (): Promise => { const credentials = getBrowserCredentialContainer(); if (!credentials?.preventSilentAccess) return false; await credentials.preventSilentAccess(); return true; }; export const getSavedLoginCredential = async (): Promise => { if (isTauriRuntime()) return getDesktopSavedLoginCredential(); return getBrowserSavedLoginCredential(); }; export const saveLoginCredential = async (email: string, password: string): Promise => { const credential = { email: normalizeEmail(email), password }; if (!credential.email || !credential.password) return false; if (isTauriRuntime()) return saveDesktopLoginCredential(credential); return saveBrowserLoginCredential(credential); }; export const clearLoginCredential = async (): Promise => { if (isTauriRuntime()) return clearDesktopLoginCredential(); return clearBrowserLoginCredential(); };