From 34b150b77e076df0c6beef99c2761ea4b635e7a8 Mon Sep 17 00:00:00 2001 From: Dylan De Faoite Date: Fri, 16 Oct 2026 20:57:02 +0100 Subject: [PATCH] feat(frontend): refresh token handling (#54) Reviewed-on: https://gitea.defaoite.dev/dylan/vod-system/pulls/54 --- frontend/src/auth/AuthContext.ts | 4 +- frontend/src/auth/AuthProvider.tsx | 97 ++++++++++++++++++++++++++---- frontend/src/utils/api/client.ts | 65 +++++++++++++++++--- frontend/src/utils/api/users.ts | 28 ++++++--- 4 files changed, 163 insertions(+), 31 deletions(-) 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