diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0a4ecb3..49e69a2 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -10,6 +10,7 @@ import { type Conversation } from "./components/layout/HistorySidebar" import { registerUnauthorizedCallback } from "./services/api" import { Button } from "./components/ui/button" import { ThemeProvider, useTheme } from "./components/theme-provider" +import { useSilentRefresh } from "./hooks/use-silent-refresh" // --- Auth Context --- interface AuthContextType { @@ -52,6 +53,8 @@ function AppContent() { const { setThemeLocal } = useTheme() const { isAuthenticated, setIsAuthenticated, user, setUser } = useAuth() + useSilentRefresh(isAuthenticated) + const [authMode, setAuthMode] = useState<"login" | "register">("login") const [isLoading, setIsLoading] = useState(true) const [selectedThreadId, setSelectedThreadId] = useState(null) diff --git a/frontend/src/hooks/use-silent-refresh.ts b/frontend/src/hooks/use-silent-refresh.ts new file mode 100644 index 0000000..35782e1 --- /dev/null +++ b/frontend/src/hooks/use-silent-refresh.ts @@ -0,0 +1,33 @@ +import { useEffect, useCallback } from 'react' +import { AuthService } from '@/services/auth' + +/** + * Hook to handle silent token refresh in the background. + * It proactively refreshes the session to prevent expiration while the user is active. + */ +export function useSilentRefresh(isAuthenticated: boolean) { + const refresh = useCallback(async () => { + try { + console.log('Proactively refreshing session...') + await AuthService.refreshSession() + } catch (error) { + console.error('Silent refresh failed:', error) + // If refresh fails, we don't necessarily logout here, + // as the reactive interceptor will handle 401s if the session is truly dead. + } + }, []) + + useEffect(() => { + if (!isAuthenticated) return + + // Refresh every 25 minutes (access token defaults to 30 mins) + const REFRESH_INTERVAL = 25 * 60 * 1000 + + const intervalId = setInterval(refresh, REFRESH_INTERVAL) + + // Also refresh immediately on mount/auth if we want to ensure we have a fresh start + // refresh() + + return () => clearInterval(intervalId) + }, [isAuthenticated, refresh]) +} diff --git a/frontend/src/services/auth.ts b/frontend/src/services/auth.ts index e49dffb..25d4eb2 100644 --- a/frontend/src/services/auth.ts +++ b/frontend/src/services/auth.ts @@ -52,6 +52,11 @@ export const AuthService = { await api.post("/auth/logout") }, + async refreshSession(): Promise { + const response = await api.post("/auth/refresh") + return response.data + }, + async updateTheme(theme: Theme): Promise { const response = await api.patch("/auth/theme", { theme }) return response.data