feat(frontend): Implement silent refresh logic
This commit is contained in:
@@ -10,6 +10,7 @@ import { type Conversation } from "./components/layout/HistorySidebar"
|
|||||||
import { registerUnauthorizedCallback } from "./services/api"
|
import { registerUnauthorizedCallback } from "./services/api"
|
||||||
import { Button } from "./components/ui/button"
|
import { Button } from "./components/ui/button"
|
||||||
import { ThemeProvider, useTheme } from "./components/theme-provider"
|
import { ThemeProvider, useTheme } from "./components/theme-provider"
|
||||||
|
import { useSilentRefresh } from "./hooks/use-silent-refresh"
|
||||||
|
|
||||||
// --- Auth Context ---
|
// --- Auth Context ---
|
||||||
interface AuthContextType {
|
interface AuthContextType {
|
||||||
@@ -52,6 +53,8 @@ function AppContent() {
|
|||||||
const { setThemeLocal } = useTheme()
|
const { setThemeLocal } = useTheme()
|
||||||
const { isAuthenticated, setIsAuthenticated, user, setUser } = useAuth()
|
const { isAuthenticated, setIsAuthenticated, user, setUser } = useAuth()
|
||||||
|
|
||||||
|
useSilentRefresh(isAuthenticated)
|
||||||
|
|
||||||
const [authMode, setAuthMode] = useState<"login" | "register">("login")
|
const [authMode, setAuthMode] = useState<"login" | "register">("login")
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [selectedThreadId, setSelectedThreadId] = useState<string | null>(null)
|
const [selectedThreadId, setSelectedThreadId] = useState<string | null>(null)
|
||||||
|
|||||||
33
frontend/src/hooks/use-silent-refresh.ts
Normal file
33
frontend/src/hooks/use-silent-refresh.ts
Normal file
@@ -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])
|
||||||
|
}
|
||||||
@@ -52,6 +52,11 @@ export const AuthService = {
|
|||||||
await api.post("/auth/logout")
|
await api.post("/auth/logout")
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async refreshSession(): Promise<AuthResponse> {
|
||||||
|
const response = await api.post<AuthResponse>("/auth/refresh")
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
|
|
||||||
async updateTheme(theme: Theme): Promise<UserResponse> {
|
async updateTheme(theme: Theme): Promise<UserResponse> {
|
||||||
const response = await api.patch<UserResponse>("/auth/theme", { theme })
|
const response = await api.patch<UserResponse>("/auth/theme", { theme })
|
||||||
return response.data
|
return response.data
|
||||||
|
|||||||
Reference in New Issue
Block a user