This commit is contained in:
2026-05-09 19:56:03 +08:00
parent 052dbc248b
commit 7c8aa1e124
12 changed files with 483 additions and 59 deletions

View File

@@ -10,6 +10,7 @@ import {
import { useGameSettings } from '../../hooks/useGameSettings';
import {
AUTH_STATE_EVENT,
getStoredAccessToken,
refreshStoredAccessToken,
} from '../../services/apiClient';
import {
@@ -18,6 +19,7 @@ import {
authEntry,
type AuthLoginMethod,
type AuthRiskBlockSummary,
type AuthSessionSnapshot,
type AuthSessionSummary,
type AuthUser,
bindWechatPhone,
@@ -81,6 +83,18 @@ function normalizeAvailableLoginMethods(
: FALLBACK_LOGIN_METHODS;
}
type AuthHydrateSessionResult =
| {
kind: 'authenticated';
session: AuthSessionSnapshot & {
user: AuthUser;
};
}
| {
kind: 'guest';
session: AuthSessionSnapshot | null;
};
export function AuthGate({ children }: AuthGateProps) {
const [status, setStatus] = useState<AuthStatus>('checking');
const [user, setUser] = useState<AuthUser | null>(null);
@@ -163,6 +177,56 @@ export function AuthGate({ children }: AuthGateProps) {
setError('');
}, []);
const restoreAuthSession = useCallback(async () => {
const hadLocalAccessToken = Boolean(getStoredAccessToken());
if (hadLocalAccessToken) {
try {
const session = await getCurrentAuthUser();
if (session.user) {
const confirmedUser = session.user;
// 中文注释:已有 access token 能确认当前账号时refresh 只作为续期和每日登录埋点补强。
// refresh cookie 临时失效或代理抖动不能反向抹掉这次已确认的登录态。
void refreshStoredAccessToken({ clearOnFailure: false }).catch(
() => undefined,
);
return {
kind: 'authenticated',
session: {
...session,
user: confirmedUser,
},
} satisfies AuthHydrateSessionResult;
}
return {
kind: 'guest',
session,
} satisfies AuthHydrateSessionResult;
} catch {
// 本地 token 可能已过期或被吊销,再尝试通过 refresh cookie 补票。
}
}
await refreshStoredAccessToken({ clearOnFailure: true });
const session = await getCurrentAuthUser();
if (session.user) {
const confirmedUser = session.user;
return {
kind: 'authenticated',
session: {
...session,
user: confirmedUser,
},
} satisfies AuthHydrateSessionResult;
}
return {
kind: 'guest',
session,
} satisfies AuthHydrateSessionResult;
}, []);
const logoutCurrentSession = useCallback(async () => {
clearLocalAuthenticatedState();
try {
@@ -316,26 +380,21 @@ export function AuthGate({ children }: AuthGateProps) {
}
try {
// 中文注释:打开已登录页面也要主动轮换 refresh cookie。
// 后端只在 refresh/session 成功续期时写每日登录埋点;如果本地 access token 尚未过期,
// 仅调用 /auth/me 不会进入续期链路,导致“打开网页”没有登录埋点。
await refreshStoredAccessToken();
const restoredSession = await restoreAuthSession();
if (!isCurrentHydrate()) {
return;
}
const nextSession = await getCurrentAuthUser();
if (!isCurrentHydrate()) {
return;
}
if (!nextSession.user) {
if (restoredSession.kind === 'guest') {
setAvailableLoginMethods(
normalizeAvailableLoginMethods(nextSession.availableLoginMethods),
normalizeAvailableLoginMethods(
restoredSession.session?.availableLoginMethods,
),
);
await resolveGuestFallback();
return;
}
const nextSession = restoredSession.session;
setUser(nextSession.user);
setAvailableLoginMethods(
normalizeAvailableLoginMethods(nextSession.availableLoginMethods),
@@ -368,7 +427,7 @@ export function AuthGate({ children }: AuthGateProps) {
isActive = false;
window.removeEventListener(AUTH_STATE_EVENT, handleAuthStateChange);
};
}, [activateReadyUser]);
}, [restoreAuthSession]);
useEffect(() => {
if (!readyUser) {