casualroom/apps/fenghuo/web/components/providers/token-provider.tsx

196 lines
5.7 KiB
TypeScript
Raw Normal View History

2025-07-28 07:50:50 +08:00
// 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<void>;
clearTokens: () => void;
forceRefresh: () => Promise<void>;
}
const TokenContext = createContext<TokenContextType | null>(null);
interface TokenProviderProps {
children: ReactNode;
}
export function TokenProvider({ children }: TokenProviderProps) {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const [accessToken, setAccessToken] = useState<string | null>(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 (
<TokenContext.Provider value={value}>
{children}
</TokenContext.Provider>
);
}
export function useToken() {
const context = useContext(TokenContext);
if (!context) {
throw new Error('useToken must be used within a TokenProvider');
}
return context;
}