diff --git a/frontend/src/auth/AuthContext.ts b/frontend/src/auth/AuthContext.ts index 9684f2e..1fa7b5d 100644 --- a/frontend/src/auth/AuthContext.ts +++ b/frontend/src/auth/AuthContext.ts @@ -8,9 +8,9 @@ type AuthContextValue = { loading: boolean; /** Exchange a Google credential for a session, then load the user. */ login: (googleCredential: string) => Promise; - /** End the session and clear the user. */ + /** End the session and clear the user. The local session is cleared even if the server call fails. */ logout: () => Promise; - /** Re-fetch the current user (e.g. after a profile change). */ + /** Re-fetch the current user (e.g. after a profile change). Token refresh is handled automatically. */ refresh: () => Promise; }; diff --git a/frontend/src/auth/AuthProvider.tsx b/frontend/src/auth/AuthProvider.tsx index b1ee12e..97c5701 100644 --- a/frontend/src/auth/AuthProvider.tsx +++ b/frontend/src/auth/AuthProvider.tsx @@ -1,19 +1,66 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import type { ReactNode } from "react"; import type { User } from "../utils/types.ts"; import { login as apiLogin, logout as apiLogout, getUser } from "../utils/api/users.ts"; import { AuthContext } from "./AuthContext.ts"; -import {registerUnauthorizedHandler} from "../utils/api/client.ts"; +import { + refreshSession, + registerSessionRefreshedHandler, + registerUnauthorizedHandler, +} from "../utils/api/client.ts"; + +/** Refresh this long before the access token expires, so in-flight requests never see it lapse. */ +const REFRESH_MARGIN_MS = 60_000; +/** How soon to try again when a scheduled refresh fails because the network is down. */ +const RETRY_DELAY_MS = 30_000; /** * Owns the single source of truth for the authenticated user. Everything that * needs to know who is logged in — the layout, the top bar, protected pages — * reads it through the `useAuth` hook rather than fetching the user itself. + * + * It also keeps the session alive: the access token is refreshed shortly + * before it expires, rather than waiting for a request to fail. That matters + * for