WIP: M3 PWA/offline implementation, M4 auth (backend + frontend), D-013 faster-whisper
- M3: hand-rolled service worker (precache + runtime /ort caching), precache manifest injection, SW registration/update toast, manifest + generated icons, production build script, offline indicator in ModelPicker (D-005/D-007) - M4 backend: bun:sqlite users/sessions (D-009), Passport local strategy behind connect-compat adapter (D-008), /api/auth routes wired into dev+prod serving, Bun.password Argon2id, 38 auth tests - M4 frontend: AuthForms + useAuth + framework-free authClient, session-aware header, anonymous-first fail-silent offline handling, 18 client tests - D-013: server engine (M5) will use faster-whisper via Python sidecar; browser side stays Transformers.js (CTranslate2 has no WASM build) - bun test: 83 pass / tsc --noEmit clean - Note: M3 doc closeout (WorkBreakdown/Architecture/tests/offline.md) still in flight from a background agent; follow-up commit will finish docs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
eb72a2e646
commit
9f39a82982
38 files changed
+3857
-246
No files matched your search
@@ -0,0 +1,57 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
fetchCurrentUser,
|
||||
login as loginRequest,
|
||||
logout as logoutRequest,
|
||||
register as registerRequest,
|
||||
type AuthUser,
|
||||
} from "./authClient";
|
||||
|
||||
export interface AuthState {
|
||||
/** The signed-in user, or null while anonymous (the default). */
|
||||
user: AuthUser | null;
|
||||
/** Rejects with a UI-safe Error message on failure (e.g. 401). */
|
||||
login: (username: string, password: string) => Promise<void>;
|
||||
/** Rejects with a UI-safe Error message on failure (400/409). */
|
||||
register: (username: string, password: string) => Promise<void>;
|
||||
/** Always resolves; local state is cleared even if the server is down. */
|
||||
logout: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Session state for the M4 auth UI. The session cookie is HttpOnly, so the
|
||||
* only source of truth is GET /api/auth/me, probed once on mount.
|
||||
* `fetchCurrentUser` never rejects — 401 and network failure (offline, server
|
||||
* down) both resolve to anonymous, keeping the anonymous-first invariant:
|
||||
* a failing /me must not surface any error.
|
||||
*/
|
||||
export function useAuth(): AuthState {
|
||||
const [user, setUser] = useState<AuthUser | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
fetchCurrentUser().then((current) => {
|
||||
// Guard against StrictMode double-mounts / fast unmounts.
|
||||
if (!cancelled && current) setUser(current);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const login = useCallback(async (username: string, password: string) => {
|
||||
setUser(await loginRequest(username, password));
|
||||
}, []);
|
||||
|
||||
const register = useCallback(async (username: string, password: string) => {
|
||||
// Register signs the user in immediately (the 201 sets the cookie).
|
||||
setUser(await registerRequest(username, password));
|
||||
}, []);
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
await logoutRequest();
|
||||
setUser(null);
|
||||
}, []);
|
||||
|
||||
return { user, login, register, logout };
|
||||
}
|
||||
Reference in new issue
Block a user