refactor(auth): centralize login status sync
This commit is contained in:
@@ -9,7 +9,6 @@ import { useRouter } from "next/navigation";
|
||||
|
||||
import { MobileShell } from "@/app/_components/core";
|
||||
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
|
||||
import { useUserDispatch } from "@/stores/user/user-context";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
|
||||
import { AuthBackground, AuthPanel } from "./components";
|
||||
@@ -18,10 +17,9 @@ import styles from "./components/auth-screen.module.css";
|
||||
export function AuthScreen() {
|
||||
const state = useAuthState();
|
||||
const authDispatch = useAuthDispatch();
|
||||
const userDispatch = useUserDispatch();
|
||||
const router = useRouter();
|
||||
|
||||
// 邮箱登录成功 → 通知 User + 跳转
|
||||
// 邮箱登录成功 → 跳转;User hydrate 由根级 <UserAuthSync /> 监听 loginStatus 变化处理。
|
||||
// 用 pendingRedirect flag(不是 useRef transition detection)——
|
||||
// re-mount 时 flag 在 auth context 里持久,区分"刚按了"和"re-visit"准确无误。
|
||||
useEffect(() => {
|
||||
@@ -37,16 +35,9 @@ export function AuthScreen() {
|
||||
"[auth-screen] useEffect → router.replace(/chat) [via pendingRedirect flag]",
|
||||
);
|
||||
authDispatch({ type: "AuthClearPendingRedirect" });
|
||||
userDispatch({ type: "UserInit" });
|
||||
router.replace(ROUTES.chat);
|
||||
}
|
||||
}, [
|
||||
state.loginStatus,
|
||||
state.pendingRedirect,
|
||||
authDispatch,
|
||||
userDispatch,
|
||||
router,
|
||||
]);
|
||||
}, [state.loginStatus, state.pendingRedirect, authDispatch, router]);
|
||||
|
||||
return (
|
||||
<MobileShell>
|
||||
|
||||
@@ -10,22 +10,18 @@
|
||||
* - BrowserHintOverlay:浏览器提示
|
||||
*
|
||||
* 鉴权解耦(事件驱动):
|
||||
* - chat 机器不感知鉴权 / 不管 WebSocket —— 由 chat-screen 派生 loginStatus
|
||||
* - chat-screen 只派 3 个事件:
|
||||
* - `ChatGuestLogin` → 游客会话(断 WS = 不连)
|
||||
* - `ChatNonGuestLogin { token }` → 非游客会话(连 WS)
|
||||
* - `ChatLogout` → 登出(机器自动 cleanup WS actor)
|
||||
* - chat 机器内部用 fromCallback actor 完整管理 WS 生命周期
|
||||
* - chat 机器不感知鉴权 / 不管 WebSocket
|
||||
* - auth → chat 的生命周期同步由根级 <ChatAuthSync /> 负责
|
||||
* - ChatScreen 只派生 UI 展示所需的 isGuest
|
||||
*/
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { useAuthState } from "@/stores/auth/auth-context";
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
||||
import { UserStorage } from "@/data/storage/user/user_storage";
|
||||
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
|
||||
import { useChatState } from "@/stores/chat/chat-context";
|
||||
import { AppEnvUtil, BrowserDetector, UrlLauncherUtil } from "@/utils";
|
||||
import { ROUTE_BUILDERS, ROUTES } from "@/router/routes";
|
||||
|
||||
@@ -50,39 +46,9 @@ function deriveIsGuest(loginStatus: LoginStatus): boolean {
|
||||
return loginStatus === "guest";
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 loginStatus 派鉴权生命周期事件(chat 机器不感知 loginStatus —— 这层转换在此)
|
||||
*
|
||||
* 设计:不直接管理 WS / 不读 AuthStorage(除取 token)
|
||||
*/
|
||||
function dispatchAuthLifecycle(
|
||||
loginStatus: LoginStatus,
|
||||
chatDispatch: ReturnType<typeof useChatDispatch>,
|
||||
): void {
|
||||
if (loginStatus === "guest") {
|
||||
// 游客:派 ChatGuestLogin(chat 机器不连 WS —— 业务事实"断 WS")
|
||||
chatDispatch({ type: "ChatGuestLogin" });
|
||||
return;
|
||||
}
|
||||
if (loginStatus === "notLoggedIn") {
|
||||
// 登出:派 ChatLogout(chat 机器自动 cleanup WS actor + 清消息)
|
||||
chatDispatch({ type: "ChatLogout" });
|
||||
return;
|
||||
}
|
||||
// 非游客:拿 loginToken → 派 ChatNonGuestLogin { token }
|
||||
void (async () => {
|
||||
const tokenR = await AuthStorage.getInstance().getLoginToken();
|
||||
if (tokenR.success && tokenR.data) {
|
||||
chatDispatch({ type: "ChatNonGuestLogin", token: tokenR.data });
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
export function ChatScreen() {
|
||||
const router = useRouter();
|
||||
const state = useChatState();
|
||||
const authState = useAuthState();
|
||||
const chatDispatch = useChatDispatch();
|
||||
const [showExternalBrowserDialog, setShowExternalBrowserDialog] =
|
||||
useState(false);
|
||||
|
||||
@@ -98,37 +64,7 @@ export function ChatScreen() {
|
||||
const showExhaustedBanner =
|
||||
isGuest && state.quotaLoaded && state.quotaExceededTrigger > 0;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// loginStatus 变化 → 派对应鉴权生命周期事件
|
||||
//
|
||||
// 规则:
|
||||
// - AuthInit 未完成 → 等,避免初始 notLoggedIn 把 /chat 变成死状态
|
||||
// - AuthInit 完成后仍 "notLoggedIn" → 清 chat 并回 splash
|
||||
// - 登录态变化(guest→email 等)→ 派 ChatGuestLogin 或 ChatNonGuestLogin
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const prevLoginStatusRef = useRef<LoginStatus | null>(null);
|
||||
const externalBrowserPromptShownRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!authState.hasInitialized || authState.isLoading) return;
|
||||
|
||||
const prev = prevLoginStatusRef.current;
|
||||
if (prev === authState.loginStatus) return; // 登录态没变
|
||||
prevLoginStatusRef.current = authState.loginStatus;
|
||||
|
||||
if (authState.loginStatus === "notLoggedIn") {
|
||||
chatDispatch({ type: "ChatLogout" });
|
||||
router.replace(ROUTES.splash);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchAuthLifecycle(authState.loginStatus, chatDispatch);
|
||||
}, [
|
||||
authState.hasInitialized,
|
||||
authState.isLoading,
|
||||
authState.loginStatus,
|
||||
chatDispatch,
|
||||
router,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!authState.hasInitialized || authState.isLoading) return;
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { signOut } from "next-auth/react";
|
||||
|
||||
import { MobileShell, SettingsSection } from "@/app/_components/core";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
|
||||
import { useUserDispatch, useUserState } from "@/stores/user/user-context";
|
||||
|
||||
@@ -36,22 +33,8 @@ export function SidebarScreen() {
|
||||
const userDispatch = useUserDispatch();
|
||||
const auth = useAuthState();
|
||||
const authDispatch = useAuthDispatch();
|
||||
const router = useRouter();
|
||||
const isLogoutRedirectPendingRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
userDispatch({ type: "UserInit" });
|
||||
}, [userDispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLogoutRedirectPendingRef.current) return;
|
||||
if (auth.isLoading || auth.loginStatus !== "notLoggedIn") return;
|
||||
isLogoutRedirectPendingRef.current = false;
|
||||
router.replace(ROUTES.splash);
|
||||
}, [auth.isLoading, auth.loginStatus, router]);
|
||||
|
||||
const handleLogoutClick = () => {
|
||||
isLogoutRedirectPendingRef.current = true;
|
||||
userDispatch({ type: "UserClearLocal" });
|
||||
authDispatch({ type: "AuthLogoutSubmitted" });
|
||||
void signOut({ redirect: false });
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useRouter } from "next/navigation";
|
||||
|
||||
import { MobileShell } from "@/app/_components/core";
|
||||
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
|
||||
import { useUserDispatch } from "@/stores/user/user-context";
|
||||
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
import { pwaUtil } from "@/utils";
|
||||
@@ -27,7 +26,6 @@ export function SplashScreen() {
|
||||
const router = useRouter();
|
||||
const state = useAuthState();
|
||||
const authDispatch = useAuthDispatch();
|
||||
const userDispatch = useUserDispatch();
|
||||
|
||||
const handleSkip = () => {
|
||||
authDispatch({ type: "AuthGuestLoginSubmitted" });
|
||||
@@ -81,16 +79,9 @@ export function SplashScreen() {
|
||||
"[splash] useEffect ② → router.replace(/chat) [via pendingRedirect flag]",
|
||||
);
|
||||
authDispatch({ type: "AuthClearPendingRedirect" });
|
||||
userDispatch({ type: "UserInit" });
|
||||
router.replace(ROUTES.chat);
|
||||
}
|
||||
}, [
|
||||
state.loginStatus,
|
||||
state.pendingRedirect,
|
||||
authDispatch,
|
||||
userDispatch,
|
||||
router,
|
||||
]);
|
||||
}, [state.loginStatus, state.pendingRedirect, authDispatch, router]);
|
||||
|
||||
return (
|
||||
<MobileShell background="var(--color-sidebar-background)">
|
||||
|
||||
Reference in New Issue
Block a user