65 lines
2.4 KiB
TypeScript
65 lines
2.4 KiB
TypeScript
// 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 };
|
|
|
|
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<string, unknown>;
|
|
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,
|
|
};
|
|
}
|
|
|
|
/** GET /api/session: the person, or null when signed out or unreachable. */
|
|
export async function loadSession(f: Fetch = fetch): Promise<Person | null> {
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE /api/session: sign out. Resolves once the server has cleared the
|
|
* session and cookie. A failed request still returns the UI to signed-out,
|
|
* because the local session view is no longer trustworthy.
|
|
*/
|
|
export async function signOut(f: Fetch = fetch): Promise<void> {
|
|
try {
|
|
await f("/api/session", { method: "DELETE", credentials: "same-origin", cache: "no-store" });
|
|
} catch {
|
|
// Ignore: the caller returns to the sign-in screen regardless.
|
|
}
|
|
}
|
|
|
|
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<SignInResult> {
|
|
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 };
|
|
}
|
|
}
|