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

196 lines
5.7 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;
}