b67beaeae6
AuthReset was declared in the AuthEvent union and handled in auth-machine.idle.on with `assign(() => initialState)`, but nothing in the codebase actually dispatches it anymore. History of dispatch sites (now all gone): - sidebar-screen.tsx previously dispatched AuthReset post-logout alongside router.replace(ROUTES.chat) — that was simplified to just dispatch AuthLogoutSubmitted, the state machine's loggingOut state now does the context reset itself in onDone via the same `assign(() => initialState)` action. - chat-screen / splash / auth screens never dispatched it. - The /auth screen's pendingRedirect-based redirect loop doesn't need it (the redirect is one-shot, not a state that needs resetting). So the event handler is unreachable. Remove the type member from auth-events.ts and the handler from auth-machine.ts. No behavior change: the only path that 'reset auth state' was already covered by loggingOut.onDone. Verified via `grep -rn 'AuthReset' src/`: only 2 hits remain after this commit — both are the type declaration and the machine handler, which is now also gone (zero hits).
307 lines
12 KiB
TypeScript
307 lines
12 KiB
TypeScript
/**
|
||
* 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,
|
||
logoutActor,
|
||
} 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
|
||
// `initializing` 不设 flag —— 启动 init 是被动读 token,不算"用户意图"。
|
||
// ============================================================
|
||
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,
|
||
logout: logoutActor,
|
||
},
|
||
}).createMachine({
|
||
/** @xstate-layout N4IgpgJg5mDOIC5QEMCuAXAFgOgJYQBswBiAQQ0wAVkA7MAgWQHsIwBhTWmCAbQAYAuolAAHJrFzpcTGsJAAPRABYA7NgAc6gEzqAjCvUBOLbvUqlWgDQgAnohWnsAZgCsSpXyeHdTgGy6lXwBfIOs0LDxCEnIsZlYOLkh+ISQQMQkpGTlFBF81Y1MzX3UvdT5NaztcrS1sD10tQ1dvPi0+YNCQcJx8IjIKADEmACcAWzYiZGGkwTl0yWlZVJzzbHMarSUvNpctYsrEXd86pT0SvfULXyUQsIpIvpjMACU4MHRkufEFrOXEXT4fGwvkMLicmxUTT4qj4hgOCC2QKUoL4KkaShc5ycty69160QoAEkaJJPql5pklqAcl4nGtDCCtGDdqotCp4YY9MC0Xw3CpfH5Gi4cd0HgSsABxVBwdAAGSYUFwNAAyqgAEajSToGYpUTfSnZRC0+mM5kQtnwlzlDQuQynEpOJz6BoqEV4qL9LAAUVGyFwBHlipV6s16G1vFm5P1i0NCGNkNNrnN7Ns9gx2D2LhUDjcl0BN06ovxnswPr9BFeitg2uGqo1Wp1XwyMb+caaJuMZtZKaquhBQKMsOhHiafY6dwixaeEqYCqIgaVddD4bJeubv2pRvbCc7Se78P0SjqYLaewuTtObsnHqeA2QAGMwGrZwBrBfB+thxtR9dUhRbukdyZPc0R7RAaiPUEnRKREjH0dQrx6G8KFIEQRHnBVFxDBsI11NJow3f8EVqVRfCzJ14L4Qp4UdI83F0Bkz3BJxtEQsUSxnOcwGVGwaHvJccNXfDf1jeMGV3FlQIPDEj1OQITAYjwijYqdBgfJ9Xx4viBK-XCmx+P8aW3cTgMki1UwQBpHFRB1IQMdRMXHXFr0eCgJjAKZKDAGgICVKBXl86Z7w+SM1wM2MLGwQFopimKAnhLQWOwTFNF2JxAWuDwtBUkkpGQAhcAALz84gIBkMA8BoAA3JgXwqotctwfKir8hAlRq+9kEpZIhIpFtNwRAVkt8JkdCaXxWhMeFNHUNZTEMTwXExaEXF0HKFma4qaCgYgwGGYYRmwdCuoAMxGUZsAajaCq2qA2uqphOu6wReoIwzlGubAFoFUw3BMEpQXhSEXDWMiVDSlkJtdQt7gIJhkF87apRld9SvKyqarqy7YfhxGoGR6t33ujqusWHrQuE8LWytIFwT0fxAW0F14SUXQQe+x0HDZRy7TYuGEb8gm5Uwmhdv2w7jvQM6xmxiJ+bxoWifax7SZkcm8L6wicitXRuQYq1-C2cwXBomovrBX7jDaVonNFeW-LLf1UbKugMdq+qcYF7bHYDEXiZV56BFekTqdaPXxs5PJdGj+EyNqJ0z08Cx6b53GHd9J2RbFg7hiOghTvO2WcHt72M99oN-aesmXopzX3oQK0jyolxrkS05JuzS1bWcW1Am8Jw0SdW3Pbxn3K1was9rR13laxu209L8tx8n4ZK9Vmh1f0g1WwsXWLacEcjAPxuD1tfIB-B8FPBUcFsphuWF6gMewCrGts4l-OpcL+evafsvl5rGvQOwcqYDQsLUMwjcBQGxbmBSyjcvqXAmgxTkAQDCp1-gAeSeNPCqs8PYPywU8IB1cg61zehFUEyUDCmD8M6TQ-IErg2PEPCwB9QQmAwXjbBFB3650ltLC6P9uHEOVlXNWNcNYUJ3gyL6-1NCGAMDTPw0097RVWrvCEAQ2KwF4veQWs4oBEAAEIPjqj5XBbs573F0XxAxXFTH3nMRAEhEiyFSJDmArYUU2Y-VMCCTQugDx0OSheEEnIHKeDvhOHAtj9FI0MSYsx3kIB8LzgXGWoo4n2KMWARxzjXEb0kVvfqRF3BHmaBYVEo4TYWQaMUahJRTAGEaIUHRei-J3kfM+Wq+SUmWPwUXbA2TtpdI0r05JPlCmbx-KAspexnANHIiYTkqIBQHiWdgJZ-jszgnWO0uxoz1I9JfH0ixe0c7pK-pkmxHSjndNfGclxYj14zLCtvMBbJgQtzZA5PQ0cmgHiiVsnkrhYHFE5CEToNAWBwDkN0EpWtEAAFp2aGHRRizF6L1C+HhMi4eLkwCIvrpsowlxsxgjRAEUwqjDDOFRBoxKI1ygIXvj0Rqm0-LEtjAEnudo-DjQ8CNVRQJo43zWQKR0pguGC2lITEW3LWy8sqQKvwQqrAWU5vS1QrNEo6BvgS4uj8fbvkVQNZVvdVXXHaBqqo5E5oAl8GDXkvyZWL39AAvaZqiIWv5SCNVNrT4MWcHQ8iQ5mluqgDwrA3qci+q2P661wq6lh30E68pmh3CbGxGy4Zdz8aJLyZMiAsbEDxqteqg8OhajGE5OYWEA8jDQxiXmw5UAxknKeaW3InI+UJsFYGlNgRkqJVtOUJRE1WUhCAA */
|
||
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,
|
||
}),
|
||
},
|
||
AuthLogoutSubmitted: "loggingOut",
|
||
// 启动一次性 init:从 storage 同步 loginStatus 到状态机 —— 由 <AuthStatusChecker /> 派发
|
||
AuthInit: "initializing",
|
||
|
||
AuthGuestLoginSubmitted: "loadingGuestLogin",
|
||
AuthEmailLoginSubmitted: "loadingEmailLogin",
|
||
AuthEmailRegisterSubmitted: "loadingEmailRegister",
|
||
AuthGoogleLoginSubmitted: "loadingOAuth",
|
||
AuthFacebookLoginSubmitted: "loadingOAuth",
|
||
AuthAppleLoginSubmitted: {
|
||
actions: assign({
|
||
errorMessage: "Apple login not implemented",
|
||
}),
|
||
},
|
||
|
||
// OAuth 回调后 sync 入口 —— 由 <OAuthSessionSync /> 派发
|
||
AuthGoogleSyncSubmitted: "syncingGoogleBackend",
|
||
AuthFacebookSyncSubmitted: "syncingFacebookBackend",
|
||
// splash / auth screen 跳完 /chat 后清flag
|
||
AuthClearPendingRedirect: {
|
||
actions: assign({ pendingRedirect: false }),
|
||
},
|
||
},
|
||
},
|
||
|
||
initializing: {
|
||
entry: assign({ errorMessage: null }),
|
||
invoke: {
|
||
src: "checkAuthStatus",
|
||
onDone: {
|
||
target: "idle", // ← init 完回 idle(从 storage 拿到 loginStatus 后落地)
|
||
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,
|
||
// Skip 点击后已在 splash 里立即跳 /chat;guest login 不再依赖 pendingRedirect。
|
||
pendingRedirect: false,
|
||
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),
|
||
}),
|
||
},
|
||
},
|
||
},
|
||
|
||
loggingOut: {
|
||
entry: assign({ errorMessage: null, pendingRedirect: false }),
|
||
invoke: {
|
||
src: "logout",
|
||
onDone: {
|
||
target: "idle",
|
||
actions: assign(() => initialState),
|
||
},
|
||
onError: {
|
||
target: "idle",
|
||
actions: assign(({ event }) => ({
|
||
...initialState,
|
||
errorMessage:
|
||
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;
|