// apps/web/lib/hooks/use-token.ts 'use client'; import { createContext, useContext, useEffect, useState, useCallback, ReactNode } from 'react'; import { useRouter, usePathname, useSearchParams } from 'next/navigation'; import { TokenManager } from '@/lib/auth/token-manager'; interface TokenContextType { accessToken: string | null; isTokenValid: boolean; refreshToken: () => Promise; clearTokens: () => void; forceRefresh: () => Promise; } const TokenContext = createContext(null); interface TokenProviderProps { children: ReactNode; } export function TokenProvider({ children }: TokenProviderProps) { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); const [accessToken, setAccessToken] = useState(null); const [isTokenValid, setIsTokenValid] = useState(false); // 加载和验证token const loadToken = useCallback(async () => { try { const token = await TokenManager.getValidAccessToken(); setAccessToken(token); setIsTokenValid(!!token && !TokenManager.isTokenExpired()); } catch (error) { console.error('加载token失败:', error); setAccessToken(null); setIsTokenValid(false); } }, []); // 刷新token const refreshToken = useCallback(async () => { await loadToken(); }, [loadToken]); // 强制刷新token状态(用于手动同步) const forceRefresh = useCallback(async () => { await loadToken(); }, [loadToken]); // 清除token const clearTokens = useCallback(() => { TokenManager.clearTokens(); setAccessToken(null); setIsTokenValid(false); }, []); // 清除登录状态并跳转到登录页面 const handleTokenExpired = useCallback(() => { // 处理token过期重新登录 clearTokens(); // 检查当前是否已经在登录页面,避免循环重定向 const currentPath = pathname + (searchParams.toString() ? `?${searchParams.toString()}` : ''); // 如果已经在登录页面,不进行重定向 if (pathname.includes('/auth/login') || pathname.includes('/auth/register')) { return; } // 获取原始重定向URL,避免嵌套 const getOriginalRedirectUrl = (): string => { const existingRedirectUrl = searchParams.get('redirectUrl'); if (existingRedirectUrl) { try { const decoded = decodeURIComponent(existingRedirectUrl); // 如果已存在的redirectUrl包含登录页面,提取其中的原始redirectUrl if (decoded.includes('/auth/login')) { const nestedUrl = new URL(decoded, window.location.origin); return nestedUrl.searchParams.get('redirectUrl') || '/dashboard'; } return decoded; } catch { return '/dashboard'; } } return currentPath; }; const originalRedirectUrl = getOriginalRedirectUrl(); const loginUrl = `/auth/login?redirectUrl=${encodeURIComponent(originalRedirectUrl)}`; // 跳转到登录页面 router.push(loginUrl); }, [clearTokens, router, pathname, searchParams]); // 页面获得焦点时检测token有效性 const checkTokenOnFocus = useCallback(async () => { // 如果当前在登录页面,不执行token检查 if (pathname.includes('/auth/login')) { return; } const currentToken = TokenManager.getAccessToken(); if (!currentToken) { // 未找到token,跳转到登录页面 console.log('未找到token,跳转到登录页面') handleTokenExpired(); return; } // 检查token是否过期 if (TokenManager.isTokenExpired()) { // Token已过期,尝试刷新... console.log('token已经过期') try { // 尝试获取新的有效token const newToken = await TokenManager.getValidAccessToken(); if (newToken) { // Token刷新成功 console.log('Token刷新成功') setAccessToken(newToken); setIsTokenValid(true); } else { // Token刷新失败,清除登录状态 console.log('Token刷新失败,清除登录状态') handleTokenExpired(); } } catch (error) { console.error('Token刷新过程中出错:', error); handleTokenExpired(); } } else { // Token仍然有效,确保状态同步 console.log('Token仍然有效,确保状态同步') setAccessToken(currentToken); setIsTokenValid(true); } }, [handleTokenExpired, pathname]); // 初始化加载 useEffect(() => { loadToken(); }, [loadToken]); // 监听localStorage变化(跨标签页同步) useEffect(() => { const handleStorageChange = (e: StorageEvent) => { if (e.key === 'oidc_access_token') { loadToken(); } }; window.addEventListener('storage', handleStorageChange); return () => window.removeEventListener('storage', handleStorageChange); }, [loadToken]); // 监听页面获得焦点事件 useEffect(() => { const handleWindowFocus = () => { // checkTokenOnFocus(); }; // 添加焦点事件监听器 window.addEventListener('focus', handleWindowFocus); // 清理函数 return () => { window.removeEventListener('focus', handleWindowFocus); }; }, [checkTokenOnFocus]); const value = { accessToken, isTokenValid, refreshToken, clearTokens, forceRefresh, }; return ( {children} ); } export function useToken() { const context = useContext(TokenContext); if (!context) { throw new Error('useToken must be used within a TokenProvider'); } return context; }