/** * Auth 状态机(XState v5) */ import { setup, assign } from "xstate"; import type { AuthProvider } from "@/lib/auth/auth_platform"; import { AuthState, initialState } from "./auth-state"; import type { AuthEvent } from "./auth-events"; import { emailLoginActor, emailRegisterThenLoginActor, oauthSignInActor, syncGoogleBackendActor, syncFacebookBackendActor, guestLoginActor, checkAuthStatusActor, } from "./auth-actors"; // 重新导出 State / Event 类型,保持 machine 文件的公共 API 不变 export type { AuthState } from "./auth-state"; export { initialState } from "./auth-state"; export type { AuthEvent } from "./auth-events"; // ============================================================ // Machine // // 设计:所有登录/同步流跑完都回 `idle`(带正确 `loginStatus`)。 // // `pendingRedirect: boolean`("用户显式触发了登录"信号)—— // 在登录成功的 onDone 里自动置 true: // splash / auth screen 看到 `pendingRedirect && loginStatus !== "notLoggedIn"` 跳 /chat // 跳完派 `AuthClearPendingRedirect` 置 false // checkingAuthStatus 不设 flag —— 被动查 token 不算"用户意图"。 // // 注:onDone 用了内联assign(不是 helper function)—— XState v5 的 TypeScript // 推断对 helper return type 不友好;内联能保留 `state.matches("loadingXxx")` 的类型。 // ============================================================ export const authMachine = setup({ types: { context: {} as AuthState, events: {} as AuthEvent, }, actors: { emailLogin: emailLoginActor, emailRegisterThenLogin: emailRegisterThenLoginActor, oauthSignIn: oauthSignInActor, syncGoogleBackend: syncGoogleBackendActor, syncFacebookBackend: syncFacebookBackendActor, guestLogin: guestLoginActor, checkAuthStatus: checkAuthStatusActor, }, }).createMachine({ id: "auth", initial: "idle", context: initialState, states: { idle: { on: { AuthPanelModeChanged: { actions: assign({ authPanelMode: ({ event }) => event.mode, errorMessage: null, }), }, AuthModeChanged: { actions: assign({ authMode: ({ event }) => event.mode, errorMessage: null, }), }, AuthFormCleared: { actions: assign({ email: "", password: "", username: "", confirmPassword: "", errorMessage: null, }), }, AuthReset: { actions: assign(() => initialState), }, // 启动 / 恢复时检查登录态 —— 由 派发 AuthStatusCheckSubmitted: "checkingAuthStatus", AuthGuestLoginSubmitted: "loadingGuestLogin", AuthEmailLoginSubmitted: "loadingEmailLogin", AuthEmailRegisterSubmitted: "loadingEmailRegister", AuthGoogleLoginSubmitted: "loadingOAuth", AuthFacebookLoginSubmitted: "loadingOAuth", AuthAppleLoginSubmitted: { actions: assign({ errorMessage: "Apple login not implemented", }), }, // OAuth 回调后 sync 入口 —— 由 派发 AuthGoogleSyncSubmitted: "syncingGoogleBackend", AuthFacebookSyncSubmitted: "syncingFacebookBackend", // splash / auth screen 跳完 /chat 后清flag AuthClearPendingRedirect: { actions: assign({ pendingRedirect: false }), }, }, }, checkingAuthStatus: { entry: assign({ errorMessage: null }), invoke: { src: "checkAuthStatus", onDone: { target: "idle", // ← status check 完回 idle(不再假冒 success) actions: assign({ loginStatus: ({ event }) => event.output, // 不置 pendingRedirect —— 状态查询是被动读 token,不算"用户意图" errorMessage: null, }), }, onError: { target: "idle", actions: assign({ errorMessage: ({ event }) => event.error instanceof Error ? event.error.message : String(event.error), }), }, }, }, loadingGuestLogin: { entry: assign({ errorMessage: null }), invoke: { src: "guestLogin", onDone: { target: "idle", // 内联 assign —— XState v5 type inference 保留 loadingGuestLogin state 类型 actions: assign({ loginStatus: ({ event }) => event.output, pendingRedirect: true, // ← 显式登录成功 → splash / auth 看到跳 /chat errorMessage: null, }), }, onError: { target: "idle", actions: assign({ errorMessage: ({ event }) => event.error instanceof Error ? event.error.message : String(event.error), }), }, }, }, loadingEmailLogin: { entry: assign({ errorMessage: null }), invoke: { src: "emailLogin", input: ({ event }) => { if (event.type !== "AuthEmailLoginSubmitted") return { email: "", password: "" }; return { email: event.email, password: event.password }; }, onDone: { target: "idle", actions: assign({ loginStatus: ({ event }) => event.output, pendingRedirect: true, // ← 邮箱登录成功 → 跳 errorMessage: null, }), }, onError: { target: "idle", actions: assign({ errorMessage: ({ event }) => event.error instanceof Error ? event.error.message : String(event.error), }), }, }, }, loadingEmailRegister: { entry: assign({ errorMessage: null }), invoke: { src: "emailRegisterThenLogin", input: ({ event }) => { if (event.type !== "AuthEmailRegisterSubmitted") return { email: "", password: "", username: "", confirmPassword: "" }; return { email: event.email, password: event.password, username: event.username, confirmPassword: event.confirmPassword, }; }, onDone: { target: "idle", actions: assign({ loginStatus: ({ event }) => event.output, pendingRedirect: true, // ← 注册成功 → 跳 errorMessage: null, }), }, onError: { target: "idle", actions: assign({ errorMessage: ({ event }) => event.error instanceof Error ? event.error.message : String(event.error), }), }, }, }, loadingOAuth: { entry: assign({ errorMessage: null }), invoke: { src: "oauthSignIn", input: ({ event }) => { if (event.type === "AuthGoogleLoginSubmitted") return event.provider; if (event.type === "AuthFacebookLoginSubmitted") return event.provider; return "google" as AuthProvider; }, onDone: { target: "idle", }, onError: { target: "idle", actions: assign({ errorMessage: ({ event }) => event.error instanceof Error ? event.error.message : String(event.error), }), }, }, }, syncingGoogleBackend: { entry: assign({ errorMessage: null }), invoke: { src: "syncGoogleBackend", input: ({ event }) => { if (event.type !== "AuthGoogleSyncSubmitted") return { idToken: "" }; return { idToken: event.idToken }; }, onDone: { target: "idle", actions: assign({ loginStatus: ({ event }) => event.output, pendingRedirect: true, // ← OAuth sync 成功 → 跳 errorMessage: null, }), }, onError: { target: "idle", actions: assign({ errorMessage: ({ event }) => event.error instanceof Error ? event.error.message : String(event.error), }), }, }, }, syncingFacebookBackend: { entry: assign({ errorMessage: null }), invoke: { src: "syncFacebookBackend", input: ({ event }) => { if (event.type !== "AuthFacebookSyncSubmitted") return { accessToken: "" }; return { accessToken: event.accessToken }; }, onDone: { target: "idle", actions: assign({ loginStatus: ({ event }) => event.output, pendingRedirect: true, // ← OAuth sync 成功 → 跳 errorMessage: null, }), }, onError: { target: "idle", actions: assign({ errorMessage: ({ event }) => event.error instanceof Error ? event.error.message : String(event.error), }), }, }, }, }, }); export type AuthMachine = typeof authMachine;