fix auth login state race
This commit is contained in:
@@ -120,6 +120,7 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
const pendingProtectedActionRef = useRef<(() => void) | null>(null);
|
||||
const autoOpenedInviteCodeRef = useRef<string | null>(null);
|
||||
const hasRenderedPlatformContentRef = useRef(false);
|
||||
const authHydrateVersionRef = useRef(0);
|
||||
const canKeepPlatformContentMounted =
|
||||
hasRenderedPlatformContentRef.current &&
|
||||
(status === 'checking' || status === 'recovering');
|
||||
@@ -134,6 +135,7 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
|
||||
const activateReadyUser = useCallback((nextUser: AuthUser) => {
|
||||
// 受保护业务 hook 只在 readyUser 暴露后启动,必须先保证请求层能带 Bearer token。
|
||||
authHydrateVersionRef.current += 1;
|
||||
setUser(nextUser);
|
||||
setStatus('ready');
|
||||
}, []);
|
||||
@@ -141,6 +143,7 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
const clearLocalAuthenticatedState = useCallback(() => {
|
||||
// 退出动作必须先收回前端鉴权上下文,再等待后端吊销完成。
|
||||
// 否则平台壳层会在无刷新状态下继续暴露旧用户的私有作品缓存。
|
||||
authHydrateVersionRef.current += 1;
|
||||
pendingProtectedActionRef.current = null;
|
||||
setUser(null);
|
||||
setStatus('unauthenticated');
|
||||
@@ -268,11 +271,13 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
useEffect(() => {
|
||||
let isActive = true;
|
||||
|
||||
const hydrate = async () => {
|
||||
const hydrate = async (hydrateToken: number) => {
|
||||
const isCurrentHydrate = () =>
|
||||
isActive && hydrateToken === authHydrateVersionRef.current;
|
||||
const callbackResult = consumeAuthCallbackResult();
|
||||
const loadLoginOptions = async () => {
|
||||
const options = await getAuthLoginOptions();
|
||||
if (!isActive) {
|
||||
if (!isCurrentHydrate()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -285,14 +290,14 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
const resolveGuestFallback = async () => {
|
||||
try {
|
||||
await loadLoginOptions();
|
||||
if (!isActive) {
|
||||
if (!isCurrentHydrate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setUser(null);
|
||||
setStatus('unauthenticated');
|
||||
} catch (optionsError) {
|
||||
if (!isActive) {
|
||||
if (!isCurrentHydrate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -305,7 +310,7 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
}
|
||||
};
|
||||
|
||||
if (callbackResult?.error && isActive) {
|
||||
if (callbackResult?.error && isCurrentHydrate()) {
|
||||
setError(callbackResult.error);
|
||||
setShowLoginModal(true);
|
||||
}
|
||||
@@ -315,8 +320,11 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
// 后端只在 refresh/session 成功续期时写每日登录埋点;如果本地 access token 尚未过期,
|
||||
// 仅调用 /auth/me 不会进入续期链路,导致“打开网页”没有登录埋点。
|
||||
await refreshStoredAccessToken();
|
||||
if (!isCurrentHydrate()) {
|
||||
return;
|
||||
}
|
||||
const nextSession = await getCurrentAuthUser();
|
||||
if (!isActive) {
|
||||
if (!isCurrentHydrate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -339,7 +347,7 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
);
|
||||
setError(callbackResult?.error ?? '');
|
||||
} catch {
|
||||
if (!isActive) {
|
||||
if (!isCurrentHydrate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -347,11 +355,11 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
}
|
||||
};
|
||||
|
||||
void hydrate();
|
||||
void hydrate(++authHydrateVersionRef.current);
|
||||
|
||||
const handleAuthStateChange = () => {
|
||||
setStatus('checking');
|
||||
void hydrate();
|
||||
void hydrate(++authHydrateVersionRef.current);
|
||||
};
|
||||
|
||||
window.addEventListener(AUTH_STATE_EVENT, handleAuthStateChange);
|
||||
|
||||
Reference in New Issue
Block a user