diff --git a/src/components/TokenRefreshManager.tsx b/src/components/TokenRefreshManager.tsx index 428b66b..5d9ce20 100644 --- a/src/components/TokenRefreshManager.tsx +++ b/src/components/TokenRefreshManager.tsx @@ -4,6 +4,7 @@ import { useEffect } from 'react'; import { getAuthInfoFromBrowserCookie, clearAuthCookie } from '@/lib/auth'; import { TOKEN_CONFIG } from '@/lib/refresh-token'; +import { isLoginPathname, resolveLoginPath } from '@/lib/tv-mode'; /** * Token 自动刷新管理器 @@ -54,7 +55,7 @@ export function TokenRefreshManager() { // 刷新失败,先登出再跳转登录 if (response.status === 401 || response.status === 403) { // 如果在登录页面,跳过登出和跳转逻辑 - if (window.location.pathname === '/login') { + if (isLoginPathname(window.location.pathname)) { console.log('[Token] On login page, skipping logout and redirect'); return false; } @@ -69,7 +70,8 @@ export function TokenRefreshManager() { // 登出失败时清除前端cookie clearAuthCookie(); } - window.location.href = `/login?redirect=${encodeURIComponent(window.location.pathname + window.location.search)}`; + const loginPath = resolveLoginPath(window.location.pathname); + window.location.href = `${loginPath}?redirect=${encodeURIComponent(window.location.pathname + window.location.search)}`; } return false; } @@ -97,6 +99,9 @@ export function TokenRefreshManager() { // Refresh Token 已过期 if (now >= authInfo.refreshExpires) { console.log('[Token] Refresh token expired, redirecting to login'); + if (isLoginPathname(window.location.pathname)) { + return false; + } // 先登出再跳转登录 window.fetch('/api/logout', { method: 'POST', @@ -106,7 +111,8 @@ export function TokenRefreshManager() { // 登出失败时清除前端cookie clearAuthCookie(); }).finally(() => { - window.location.href = `/login?redirect=${encodeURIComponent(window.location.pathname + window.location.search)}`; + const loginPath = resolveLoginPath(window.location.pathname); + window.location.href = `${loginPath}?redirect=${encodeURIComponent(window.location.pathname + window.location.search)}`; }); return false; } @@ -152,7 +158,7 @@ export function TokenRefreshManager() { // 响应拦截:401 错误时刷新 Token 并重试(仅重试一次) if (response.status === 401) { // 如果在登录页面,跳过刷新逻辑 - if (window.location.pathname === '/login') { + if (isLoginPathname(window.location.pathname)) { console.log('[Token] On login page, skipping refresh logic'); return response; } @@ -178,7 +184,7 @@ export function TokenRefreshManager() { console.error('[Token] Still 401 after refresh, redirecting to login'); // 如果在登录页面,跳过登出和跳转逻辑 - if (window.location.pathname === '/login') { + if (isLoginPathname(window.location.pathname)) { console.log('[Token] On login page, skipping logout and redirect'); return response; } @@ -193,7 +199,8 @@ export function TokenRefreshManager() { // 登出失败时清除前端cookie clearAuthCookie(); } - window.location.href = `/login?redirect=${encodeURIComponent(window.location.pathname + window.location.search)}`; + const loginPath = resolveLoginPath(window.location.pathname); + window.location.href = `${loginPath}?redirect=${encodeURIComponent(window.location.pathname + window.location.search)}`; } } } else { diff --git a/src/lib/db.client.ts b/src/lib/db.client.ts index fdd3e62..096e021 100644 --- a/src/lib/db.client.ts +++ b/src/lib/db.client.ts @@ -17,6 +17,7 @@ import { getAuthInfoFromBrowserCookie, clearAuthCookie } from './auth'; import { normalizeEpisodeFilterConfig } from './episode-filter'; import { MangaReadRecord, MangaShelfItem } from './manga.types'; +import { isLoginPathname, resolveLoginPath } from './tv-mode'; import { DanmakuFilterConfig, EpisodeFilterConfig, SkipConfig } from './types'; // 全局错误触发函数 @@ -661,7 +662,7 @@ export async function fetchWithAuth( // 如果在登录页面,跳过刷新逻辑 if ( typeof window !== 'undefined' && - window.location.pathname === '/login' + isLoginPathname(window.location.pathname) ) { console.log('[fetchWithAuth] On login page, skipping refresh logic'); return res; @@ -707,7 +708,7 @@ export async function fetchWithAuth( // 检查当前页面是否已经是登录页,避免重复跳转 if ( typeof window !== 'undefined' && - !window.location.pathname.startsWith('/login') + !isLoginPathname(window.location.pathname) ) { // 调用 logout 接口 try { @@ -721,7 +722,10 @@ export async function fetchWithAuth( clearAuthCookie(); } const currentUrl = window.location.pathname + window.location.search; - const loginUrl = new URL('/login', window.location.origin); + const loginUrl = new URL( + resolveLoginPath(window.location.pathname), + window.location.origin + ); loginUrl.searchParams.set('redirect', currentUrl); window.location.href = loginUrl.toString(); } diff --git a/src/lib/tv-mode.ts b/src/lib/tv-mode.ts index a5ebc3e..1427f9e 100644 --- a/src/lib/tv-mode.ts +++ b/src/lib/tv-mode.ts @@ -1,3 +1,16 @@ export function isTVModeEnabled() { return process.env.ENABLE_TV_MODE !== 'false'; } + +export function isTVPathname(pathname: string): boolean { + return pathname === '/tv' || pathname.startsWith('/tv/'); +} + +/** 按当前路径选择登录页:TV 端走 /tv/login,其余走 /login */ +export function resolveLoginPath(pathname: string): string { + return isTVPathname(pathname) ? '/tv/login' : '/login'; +} + +export function isLoginPathname(pathname: string): boolean { + return pathname === '/login' || pathname === '/tv/login'; +} diff --git a/src/middleware.ts b/src/middleware.ts index 8afb5af..d53c2d4 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -5,7 +5,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { isAccessTokenInvalidated } from '@/lib/access-token-invalidation'; import { getAuthInfoFromCookie } from '@/lib/auth'; import { TOKEN_CONFIG } from '@/lib/refresh-token'; -import { isTVModeEnabled } from '@/lib/tv-mode'; +import { isTVModeEnabled, resolveLoginPath } from '@/lib/tv-mode'; export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; @@ -158,9 +158,7 @@ function handleAuthFailure( } // TV 端页面未授权时进入电视扫码登录页 - const loginUrl = pathname.startsWith('/tv') - ? new URL('/tv/login', request.url) - : new URL('/login', request.url); + const loginUrl = new URL(resolveLoginPath(pathname), request.url); // 保留完整的URL,包括查询参数 const fullUrl = `${pathname}${request.nextUrl.search}`; loginUrl.searchParams.set('redirect', fullUrl);