frontend: add session API, credentialed fetch, 401-triggered relogin

This commit is contained in:
2026-07-24 21:44:19 +02:00
parent f9990ddcc2
commit 6dd4d9309c
2 changed files with 21 additions and 0 deletions

View File

@@ -7,6 +7,7 @@ import type {
ProgressionMetric,
ProgressionPoint,
ReviewQueuePage,
SessionInfo,
SyncRun,
SyncStatus,
WorkoutKind,
@@ -17,8 +18,17 @@ const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080";
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${BASE_URL}${path}`, {
headers: { "Content-Type": "application/json" },
credentials: "include",
...init,
});
if (res.status === 401 && path !== "/api/session/me") {
// An established session expired mid-use (not the initial "am I logged
// in" check, which LoginGate itself interprets) -- reload so LoginGate
// re-checks and shows the login screen instead of leaving the SPA in a
// half-authenticated state.
window.location.href = "/";
throw new Error(`${init?.method ?? "GET"} ${path} failed: 401 (session expired)`);
}
if (!res.ok) {
const body = await res.text();
throw new Error(`${init?.method ?? "GET"} ${path} failed: ${res.status} ${body}`);
@@ -28,6 +38,12 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
}
export const api = {
// Session (app login via OIDC -- distinct from the Garmin credential
// login below). No logout()/login() methods: those are plain <a href>
// full-page navigations (see LoginGate.tsx), not fetches, since the OIDC
// flow and Keycloak's own logout redirect need real browser navigation.
getSessionInfo: () => request<SessionInfo>("/api/session/me"),
// Auth
login: () => request<AuthResponse>("/api/auth/login", { method: "POST" }),
submitMFA: (code: string) =>

View File

@@ -186,6 +186,11 @@ export interface AuthResponse {
message: string;
}
export interface SessionInfo {
name: string;
email: string;
}
export interface DetailFillProgress {
Done: number;
Total: number;