// Browser-side calls to this app's own /api/session. No token, password or // session id is stored or logged here; the session lives in an httpOnly cookie. export type Person = { name: string; email: string; pictureUrl: string | null; feedUserId?: string }; export const WRONG_CREDENTIALS = "Email or password is incorrect."; export const GENERIC_FAILURE = "Could not sign you in. Try again."; type Fetch = typeof fetch; function toPerson(body: unknown): Person | null { if (!body || typeof body !== "object") return null; const r = body as Record; if (typeof r.email !== "string") return null; return { name: typeof r.name === "string" ? r.name : "", email: r.email, pictureUrl: typeof r.pictureUrl === "string" ? r.pictureUrl : null, ...(typeof r.feedUserId === "string" && r.feedUserId ? { feedUserId: r.feedUserId } : {}), }; } /** GET /api/session: the person, or null when signed out or unreachable. */ export async function loadSession(f: Fetch = fetch): Promise { try { const res = await f("/api/session", { credentials: "same-origin", cache: "no-store" }); if (!res.ok) return null; return toPerson(await res.json()); } catch { return null; } } export type SignInResult = { ok: true; person: Person } | { ok: false; message: string }; /** POST /api/session. A 401 is always the one wrong-credentials message. */ export async function signIn(email: string, password: string, f: Fetch = fetch): Promise { try { const res = await f("/api/session", { method: "POST", credentials: "same-origin", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, password }), }); if (res.status === 401) return { ok: false, message: WRONG_CREDENTIALS }; if (!res.ok) return { ok: false, message: GENERIC_FAILURE }; const person = toPerson(await res.json()); return person ? { ok: true, person } : { ok: false, message: GENERIC_FAILURE }; } catch { return { ok: false, message: GENERIC_FAILURE }; } }