From f188b4eac4324fd2627ab7dacd5993d7043e2364 Mon Sep 17 00:00:00 2001 From: kyuuseiryuu Date: Mon, 16 Mar 2026 13:38:02 +0900 Subject: [PATCH] refactor: centralize Logto config and improve auth flow - Centralized Logto domain and API base URLs in `common.ts` to avoid duplication. - Replaced deprecated `Modal.useModal` with `App.useApp().modal` for consistent Ant Design usage. - Refactored `useAutoLogin` hook to handle token expiration checks and trigger re-authentication. - Updated `UserCenter` and `FavPlayersPage` to use the new `autoSignIn` flow. - Removed the `useAuthHeaders` hook as logic was consolidated into `useAutoLogin`. - Added `AUTH_CALLBACK_URL` and `USER_CENTER_URL` constants for cleaner routing. --- src/frontend.tsx | 13 ++-- .../{useAuthHeaders.ts => useAuthHeaders.tsx} | 12 +++- src/hooks/useAutoLogin.ts | 27 +++++--- src/page/FavPlayersPage.tsx | 9 ++- src/page/UserCenter.tsx | 66 +++++++++++-------- src/services/xcxApi.ts | 4 +- src/utils/common.ts | 3 +- src/utils/front.ts | 6 +- src/utils/server.ts | 8 +-- 9 files changed, 92 insertions(+), 56 deletions(-) rename src/hooks/{useAuthHeaders.ts => useAuthHeaders.tsx} (63%) diff --git a/src/frontend.tsx b/src/frontend.tsx index 58df33a..db83ea6 100644 --- a/src/frontend.tsx +++ b/src/frontend.tsx @@ -7,17 +7,18 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import { ConfigProvider, Empty, theme } from "antd"; +import { App, ConfigProvider, Empty, theme } from "antd"; import zhCN from 'antd/locale/zh_CN'; import { RouterProvider } from "react-router"; import { LogtoProvider, type LogtoConfig } from '@logto/react'; import { route } from "./routes"; import { LOGTO_RESOURCE } from "./utils/constants"; +import { LOGTO_DOMAIN } from "./utils/common"; const elem = document.getElementById("root")!; const config: LogtoConfig = { - endpoint: 'https://logto.ksr.la/', + endpoint: LOGTO_DOMAIN, appId: 'iq0oceaeqazpcned7hzil', resources: [LOGTO_RESOURCE], }; @@ -28,9 +29,11 @@ const app = ( locale={zhCN} renderEmpty={() => } > - - - + + + + + ); diff --git a/src/hooks/useAuthHeaders.ts b/src/hooks/useAuthHeaders.tsx similarity index 63% rename from src/hooks/useAuthHeaders.ts rename to src/hooks/useAuthHeaders.tsx index f54ad95..b626062 100644 --- a/src/hooks/useAuthHeaders.ts +++ b/src/hooks/useAuthHeaders.tsx @@ -1,17 +1,25 @@ import { useLogto } from "@logto/react" import { useEffect, useState } from "react"; import { LOGTO_RESOURCE } from "../utils/constants"; +import { App, Button } from "antd"; import useAutoLogin from "./useAutoLogin"; export const useAuthHeaders = (): HeadersInit => { const { isAuthenticated, getAccessToken } = useLogto(); - const { login } = useAutoLogin(); const [headers, setHeaders] = useState({}); + const { autoSignIn } = useAutoLogin(); + const app = App.useApp(); useEffect(() => { if (isAuthenticated) { getAccessToken(LOGTO_RESOURCE).then(token => { if (!token) { - login(); + app.notification.warning({ + key: 'use-auth-headers-login-expired', + title: '登陆已过期', + actions: [ + + ] + }) return; } setHeaders({ Authorization: `Bearer ${token}` }) diff --git a/src/hooks/useAutoLogin.ts b/src/hooks/useAutoLogin.ts index 5c4f780..c33f46e 100644 --- a/src/hooks/useAutoLogin.ts +++ b/src/hooks/useAutoLogin.ts @@ -1,15 +1,24 @@ -import { useCallback } from "react" -import { useNavigate } from "react-router"; +import { useLogto } from "@logto/react"; +import { useCallback, useEffect, useState } from "react" +import { AUTH_CALLBACK_URL } from "../utils/front"; + const useAutoLogin = () => { - const navigate = useNavigate(); - const login = useCallback((redirect = window.location.pathname) => { - if (redirect) { - sessionStorage.setItem('redirect', redirect); - } - navigate('/user-center?autoSignIn=true'); + const { isAuthenticated, getAccessToken } = useLogto(); + const [isAuthExpired, setIsAuthExpired] = useState(false); + const { signIn } = useLogto(); + const autoSignIn = useCallback((redirect?: string) => { + sessionStorage.setItem('redirect', redirect ?? window.location.pathname); + signIn(AUTH_CALLBACK_URL); }, []); - return { login }; + useEffect(() => { + if (isAuthenticated) { + getAccessToken().then(e => { + if (!e) setIsAuthExpired(true); // Assuming + }); + } + }, [isAuthenticated]); + return { autoSignIn, isAuthExpired }; } export default useAutoLogin; diff --git a/src/page/FavPlayersPage.tsx b/src/page/FavPlayersPage.tsx index d5aa877..50b0352 100644 --- a/src/page/FavPlayersPage.tsx +++ b/src/page/FavPlayersPage.tsx @@ -1,4 +1,4 @@ -import { Avatar, Button, Card, Divider, Flex, Image, message as AntdMessage, Popconfirm, Radio, Segmented, Spin, Typography } from "antd"; +import { Avatar, Button, Card, Divider, Flex, Image, message as AntdMessage, Popconfirm, Radio, Segmented, Spin, Typography, App } from "antd"; import { useFavPlayerStore } from "../store/useFavPlayerStore"; import { useNavigate } from "react-router"; import { useCallback, useEffect, useMemo, useState } from "react"; @@ -20,13 +20,12 @@ enum ShowType { } export function FavePlayersPage() { - const [message, contextHolder] = AntdMessage.useMessage(); const { favMap, unFav } = useFavPlayerStore(state => state); const [sortType, setSortType] = useState(SortType.DEFAULT); const [showType, setShowType] = useState(ShowType.LOCAL); const [claims, setClaims] = useState(); const headers = useAuthHeaders(); - const { login } = useAutoLogin(); + const { autoSignIn } = useAutoLogin(); const localList = Object.values(favMap); const favListRequest = useRequest(async (aud: string) => { const res = await fetch(`/api/fav`, { headers }); @@ -49,6 +48,7 @@ export function FavePlayersPage() { }, [favMap, sortType, showType, localList, favListRequest]); const navigate = useNavigate(); const { isAuthenticated, getIdTokenClaims } = useLogto(); + const message = App.useApp().message; useEffect(() => { if (!isAuthenticated) return; const id = setTimeout(async () => { @@ -75,7 +75,6 @@ export function FavePlayersPage() { }, []); return (
- {contextHolder} 收藏的球员 @@ -143,7 +142,7 @@ export function FavePlayersPage() { ) } { (showType === ShowType.LOCAL && !isAuthenticated && list.length > 0) && ( ) } diff --git a/src/page/UserCenter.tsx b/src/page/UserCenter.tsx index c9c6f9f..b15c51e 100644 --- a/src/page/UserCenter.tsx +++ b/src/page/UserCenter.tsx @@ -1,8 +1,11 @@ -import { EditOutlined, FileTextOutlined, HomeOutlined, KeyOutlined, LockOutlined, LoginOutlined, LogoutOutlined, MailOutlined, MobileOutlined } from "@ant-design/icons"; +import { EditOutlined, FileTextOutlined, HomeOutlined, KeyOutlined, LockOutlined, LoginOutlined, LogoutOutlined, MailOutlined, MobileOutlined, ReloadOutlined } from "@ant-design/icons"; import { useLogto, type IdTokenClaims } from "@logto/react"; -import { App, Avatar, Button, Divider, Flex, Modal, Typography } from "antd"; +import { App, Avatar, Button, Divider, Flex, Typography } from "antd"; import { useCallback, useEffect, useState } from "react"; import { useLocation, useNavigate } from "react-router"; +import { AUTH_CALLBACK_URL, USER_CENTER_URL } from "../utils/front"; +import useAutoLogin from "../hooks/useAutoLogin"; +import { LOGTO_DOMAIN } from "../utils/common"; enum modifyRoutes { username = '/account/username', @@ -13,17 +16,16 @@ enum modifyRoutes { backup_code = '/account/backup-codes/generate', } -const redirect = encodeURIComponent(`${window.location.origin}/user-center`); -const logto = 'https://logto.ksr.la'; +const redirect = encodeURIComponent(USER_CENTER_URL); export const UserCenter = () => { const { signIn, isAuthenticated, signOut, getIdTokenClaims } = useLogto(); + const { isAuthExpired, autoSignIn } = useAutoLogin(); const [user, setUser] = useState(); const navigate = useNavigate(); const location = useLocation(); const handleSignIn = useCallback(() => { - const redirect = `${window.location.origin}/auth/callback`; - signIn(redirect); + signIn(AUTH_CALLBACK_URL); }, []); useEffect(() => { if (isAuthenticated) return; @@ -37,9 +39,9 @@ export const UserCenter = () => { getIdTokenClaims().then(claims => setUser(claims)); }, [isAuthenticated]); const handleModifyInfo = useCallback((url: string) => { - window.location.href = `${logto}${url}?redirect=${redirect}`; + window.location.href = `${LOGTO_DOMAIN}${url}?redirect=${redirect}`; }, []); - const [modal, contextHolder] = Modal.useModal(); + const app = App.useApp(); if (!isAuthenticated) { return (
@@ -57,7 +59,6 @@ export const UserCenter = () => { } return ( <> - {contextHolder} @@ -73,24 +74,35 @@ export const UserCenter = () => { - + + {isAuthExpired && ( + + + + )} + + + + ); diff --git a/src/services/xcxApi.ts b/src/services/xcxApi.ts index c3e29a5..399a330 100644 --- a/src/services/xcxApi.ts +++ b/src/services/xcxApi.ts @@ -1,8 +1,8 @@ import type { GamesData, XCXFindUserResp, XCXMember, XCXProfile, XCXTag } from "../types"; -import { BASE_URL } from "../utils/common"; +import { KAIQIU_BASE_URL } from "../utils/common"; import { redis } from "../utils/server"; -const XCX_BASE_URL = `${BASE_URL}/xcx/public/index.php`; +const XCX_BASE_URL = `${KAIQIU_BASE_URL}/xcx/public/index.php`; export function createXCXHeader(token: string) { const xcxDefaultHeaders = { diff --git a/src/utils/common.ts b/src/utils/common.ts index ef74086..70a2b05 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,6 +1,7 @@ import { chunk } from "lodash"; -export const BASE_URL = `https://kaiqiuwang.cc`; +export const KAIQIU_BASE_URL = `https://kaiqiuwang.cc`; +export const LOGTO_DOMAIN = 'https://logto.ksr.la'; export function sneckGroup(size: number, groupLen: number) { const indexArray = new Array(size).fill(0).map((_, i) => i); diff --git a/src/utils/front.ts b/src/utils/front.ts index 5704757..e98d20b 100644 --- a/src/utils/front.ts +++ b/src/utils/front.ts @@ -90,4 +90,8 @@ export const openWebMapRaw = (type: MapType, location: MapLocation): void => { if (url) { window.open(url, '_blank'); } -}; \ No newline at end of file +}; + + +export const AUTH_CALLBACK_URL = `${window.location.origin}/auth/callback`; +export const USER_CENTER_URL = `${window.location.origin}/user-center`; \ No newline at end of file diff --git a/src/utils/server.ts b/src/utils/server.ts index bf85d50..4d1d4e0 100644 --- a/src/utils/server.ts +++ b/src/utils/server.ts @@ -1,7 +1,7 @@ import type { Player } from "../types"; import * as cheerio from "cheerio"; import { XCXAPI } from "../services/xcxApi"; -import { BASE_URL } from "./common"; +import { KAIQIU_BASE_URL, LOGTO_DOMAIN } from "./common"; import { RedisClient } from "bun"; import { createRemoteJWKSet, jwtVerify } from 'jose'; import { LOGTO_RESOURCE } from "./constants"; @@ -48,7 +48,7 @@ export const htmlRequestHeaders = { * @returns HTML */ export async function fetchEventContentHTML(matchId: string) { - const url = `${BASE_URL}/home/space.php?do=event&id=${matchId}&view=member&status=2`; + const url = `${KAIQIU_BASE_URL}/home/space.php?do=event&id=${matchId}&view=member&status=2`; const resp = await fetch(url, { headers: htmlRequestHeaders }); return resp.text() ?? '' } @@ -97,7 +97,7 @@ export const extractBearerTokenFromHeaders = (authorization: string | null) => { return authorization.slice(7); // The length of 'Bearer ' is 7 }; -const jwks = createRemoteJWKSet(new URL('https://logto.ksr.la/oidc/jwks')); +const jwks = createRemoteJWKSet(new URL(`${LOGTO_DOMAIN}/oidc/jwks`)); export const verifyLogtoToken = async (headers: Headers) => { const auth = headers.get('Authorization'); @@ -110,7 +110,7 @@ export const verifyLogtoToken = async (headers: Headers) => { jwks, { // Expected issuer of the token, issued by the Logto server - issuer: 'https://logto.ksr.la/oidc', + issuer: `${LOGTO_DOMAIN}/oidc`, // Expected audience token, the resource indicator of the current API audience: LOGTO_RESOURCE, }