b5dac8886f
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
// Typed fetch wrappers for the /api/auth/* surface (SD-0001 §6.4). Same-origin via the Vite
|
|
// proxy; cookies carry the session. Errors return the §6.4 envelope; helpers normalize them.
|
|
import type { SessionState } from "./routing";
|
|
|
|
export interface ApiError {
|
|
code: string;
|
|
message: string;
|
|
retry_after_s?: number;
|
|
attempts_remaining?: number;
|
|
}
|
|
|
|
export interface VerifyResult {
|
|
account: { email: string };
|
|
storefront: { id: number; name: string } | null;
|
|
created: boolean;
|
|
}
|
|
|
|
export async function errorOf(resp: Response): Promise<ApiError> {
|
|
try {
|
|
const body = await resp.json();
|
|
if (body && body.error) return body.error as ApiError;
|
|
} catch {
|
|
/* fall through */
|
|
}
|
|
return { code: "unexpected", message: "Something went wrong. Please try again." };
|
|
}
|
|
|
|
export async function getMe(): Promise<SessionState | null> {
|
|
const resp = await fetch("/api/auth/me", { credentials: "include" });
|
|
if (resp.status === 401) return null;
|
|
if (!resp.ok) return null;
|
|
return (await resp.json()) as SessionState;
|
|
}
|
|
|
|
export async function requestCode(email: string): Promise<ApiError | null> {
|
|
const resp = await fetch("/api/auth/request-code", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
credentials: "include",
|
|
body: JSON.stringify({ email }),
|
|
});
|
|
return resp.ok ? null : await errorOf(resp);
|
|
}
|
|
|
|
export async function verifyCode(
|
|
email: string,
|
|
code: string,
|
|
): Promise<{ ok: true; result: VerifyResult } | { ok: false; error: ApiError }> {
|
|
const resp = await fetch("/api/auth/verify", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
credentials: "include",
|
|
body: JSON.stringify({ email, code }),
|
|
});
|
|
if (resp.ok) return { ok: true, result: (await resp.json()) as VerifyResult };
|
|
return { ok: false, error: await errorOf(resp) };
|
|
}
|
|
|
|
export async function logout(): Promise<void> {
|
|
await fetch("/api/auth/logout", { method: "POST", credentials: "include" });
|
|
}
|
|
|
|
export interface StorefrontResult {
|
|
id: number;
|
|
name: string;
|
|
}
|
|
|
|
export async function createStorefront(
|
|
name: string,
|
|
): Promise<{ ok: true; storefront: StorefrontResult } | { ok: false; error: ApiError }> {
|
|
const resp = await fetch("/api/storefronts", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
credentials: "include",
|
|
body: JSON.stringify(name.trim() ? { name: name.trim() } : {}),
|
|
});
|
|
if (resp.ok) return { ok: true, storefront: (await resp.json()) as StorefrontResult };
|
|
return { ok: false, error: await errorOf(resp) };
|
|
}
|