0a9abc2502
Root cause: backend `/auth/logout` returns `{ success: true, data: null }`
(no business payload for a fire-and-forget endpoint). The previous
`AuthApi.logout` ran `unwrap(env)` (which passes null through, since
null is defined) and then `LogoutResponse.fromJson(null)`, which
Zod-parses as `z.object(...)` and throws "Invalid input: expected
object, received null".
The error was caught in `AuthRepository.logout` with
`console.warn(... clearing local anyway)` — logout actually still
worked (local clear ran), but a noisy red ZodError hit the console on
every logout click.
Fix: align `AuthApi.logout` with the existing fire-and-forget pattern
used by `register` and `sendCode` — call `httpClient` and ignore the
response body. Drop the now-unused `LogoutResponse` import.
`AuthRepository.logout` already discards the return value, so the
`Promise<LogoutResponse> → Promise<void>` signature change is safe.
Bundled in this commit (unrelated):
- chat-machine.actors.ts / user-machine.ts: removed stale design-doc
comment blocks (IDE/linter cleanup)
- user-machine.actors.ts: removed redundant `userStorage.clearUserData()`
from `userLogoutActor` — `authRepo.logout` already clears local
user data, so this was a no-op duplicate.
- sidebar-screen.tsx: removed one stale comment line.
73 lines
3.0 KiB
TypeScript
73 lines
3.0 KiB
TypeScript
/**
|
||
* User 状态机:Actors(异步服务)
|
||
*
|
||
* 从 `user-machine.ts` 抽出的"Actors"段:
|
||
* - `userInitActor`(fromPromise:读 UserStorage 并行 user + avatarUrl)
|
||
* - `userFetchActor`(fromPromise:调 API + 持久化到本地)
|
||
* - `userLogoutActor`(fromPromise:auth logout + 清本地数据)
|
||
*
|
||
* 设计:
|
||
* - 依赖 `user-machine.helpers.ts`(toView, readInitData, userStorage)
|
||
* - 仓库以接口类型注入(调用面只看接口,运行时仍是同一单例)
|
||
* - 命名约定:`[user-machine]` 前缀日志(如未来加)—— 与 chat 模块对齐
|
||
*/
|
||
|
||
import { fromPromise } from "xstate";
|
||
|
||
import type { UserView } from "@/models/user/user";
|
||
import { userRepository } from "@/data/repositories/user_repository";
|
||
import { authRepository } from "@/data/repositories/auth_repository";
|
||
import type {
|
||
IAuthRepository,
|
||
IUserRepository,
|
||
} from "@/data/repositories/interfaces";
|
||
import { Result } from "@/utils/result";
|
||
|
||
import { type InitData, readInitData, toView, userStorage } from "./user-machine.helpers";
|
||
|
||
// ============================================================
|
||
// 仓库注入
|
||
// ============================================================
|
||
// 仓库以接口类型注入:调用面只看接口,运行时仍是同一单例
|
||
const userRepo: IUserRepository = userRepository;
|
||
const authRepo: IAuthRepository = authRepository;
|
||
|
||
// ============================================================
|
||
// Init actor:从 UserStorage 读 user + avatarUrl
|
||
// ============================================================
|
||
/**
|
||
* 读本地 user + avatarUrl(并行)。
|
||
* - 失败/无数据 → 返回 null,不抛错(machine 走 onDone 回到 idle,context 是 null)
|
||
* - onDone 赋 currentUser + avatarUrl 到 context
|
||
*/
|
||
export const userInitActor = fromPromise<InitData>(async () => readInitData());
|
||
|
||
// ============================================================
|
||
// Fetch actor:调 API 拉当前用户 + 持久化
|
||
// ============================================================
|
||
/**
|
||
* 调 `userRepo.getCurrentUser` 拉当前 user:
|
||
* - 成功 → 转 UserView + 写本地(userStorage.setUser)→ 返回 View
|
||
* - 失败 → 返回 null(machine 走 onDone,context 保持不变;onError 兜底)
|
||
*/
|
||
export const userFetchActor = fromPromise<UserView | null>(async () => {
|
||
const result = await userRepo.getCurrentUser();
|
||
if (Result.isErr(result)) return null;
|
||
const view = toView(result.data.toJson());
|
||
// 持久化到本地
|
||
await userStorage.setUser(result.data.toJson());
|
||
return view;
|
||
});
|
||
|
||
// ============================================================
|
||
// Logout actor:后端登出 + 清本地用户数据
|
||
// ============================================================
|
||
/**
|
||
* 登出:
|
||
* - 调 `authRepo.logout` 让后端吊销 token
|
||
* - `userStorage.clearUserData` 清本地 user / avatar 等
|
||
* - onDone / onError 都跑 `clearUser` action(machine 层)—— 失败也清本地
|
||
*/
|
||
export const userLogoutActor = fromPromise<void>(async () => {
|
||
await authRepo.logout();
|
||
}); |