"use client"; /** * AuthRepository * * 认证仓库:编排远程 API + 本地 AuthStorage + UserStorage。 * 行为对齐原 Dart `lib/data/repositories/auth_repository_impl.dart`: * - 登录成功后调 `_saveLoginData` 持久化 token + User * - 登出先调 API,再**强制**清本地态(无论 API 成败) * - `getCurrentUser` 成功后**尽力**写本地 User 缓存(不因缓存失败影响主流程) * - 写 storage 失败用 `console.warn` 记录,不让本地缓存失败影响主流程 * * 原始 Dart: lib/data/repositories/auth_repository_impl.dart */ import { AuthApi, authApi } from "@/data/api"; import { AppleLoginRequest } from "@/data/dto/auth/apple_login_request"; import { FacebookLoginRequest } from "@/data/dto/auth/facebook_login_request"; import { FbIdLoginRequest } from "@/data/dto/auth/fb_id_login_request"; import { GoogleLoginRequest } from "@/data/dto/auth/google_login_request"; import { GuestLoginRequest } from "@/data/dto/auth/guest_login_request"; import { GuestLoginResponse } from "@/data/dto/auth/guest_login_response"; import { LoginRequest } from "@/data/dto/auth/login_request"; import { LoginResponse } from "@/data/dto/auth/login_response"; import { RefreshTokenRequest } from "@/data/dto/auth/refresh_token_request"; import { RefreshTokenResponse } from "@/data/dto/auth/refresh_token_response"; import { RegisterRequest } from "@/data/dto/auth/register_request"; import { SendCodeRequest } from "@/data/dto/auth/send_code_request"; import { User } from "@/data/dto/user/user"; import { Result } from "@/utils/result"; import { AuthStorage } from "@/data/storage/auth/auth_storage"; import type { IAuthStorage } from "@/data/storage/auth/iauth_storage"; import { UserStorage } from "@/data/storage/user/user_storage"; import type { IUserStorage } from "@/data/storage/user/iuser_storage"; /** 硬编码平台名,对齐 Dart `PlatformUtil.platformName.toLowerCase()`(Web 平台)。 */ const WEB_PLATFORM = "web"; export class AuthRepository { constructor( private readonly api: AuthApi, private readonly storage: IAuthStorage, private readonly userStorage: IUserStorage, ) {} // ============ 公共方法 ============ /** * 用户注册。 * 注册成功不会自动登录(与 Dart 行为一致),调用方需自行调 `emailLogin`。 */ async register(input: { username: string; email: string; password: string; guestId?: string; }): Promise> { return Result.wrap(async () => { await this.api.register( RegisterRequest.from({ username: input.username, email: input.email, password: input.password, platform: WEB_PLATFORM, guestId: input.guestId ?? "", }), ); }); } /** 邮箱/用户名 + 密码登录。 */ async emailLogin(input: { email?: string; username?: string; password: string; guestId?: string; }): Promise> { return Result.wrap(async () => { const response = await this.api.emailLogin( LoginRequest.from({ email: input.email ?? "", username: input.username ?? "", password: input.password, platform: WEB_PLATFORM, guestId: input.guestId ?? "", }), ); await this._saveLoginData(response); return response; }); } /** 发送邮箱验证码。 */ async sendCode(email: string): Promise> { return Result.wrap(async () => { await this.api.sendCode(SendCodeRequest.from({ email })); }); } /** * 退出登录:先调 API 注销服务端会话,**无论成败**都清本地登录态。 * 强制清本地的逻辑:用户即使在 API 失败时也想从设备上登出。 */ async logout(): Promise> { try { await this.api.logout(); } catch (e) { // API 失败也要继续清本地态 console.warn("[AuthRepository] logout API failed, clearing local anyway", e); } try { await this.storage.clearAuthData(); await this.userStorage.clearUserData(); return Result.ok(undefined); } catch (e) { // 清本地是 best-effort;理论上 LS.remove 不应失败 return Result.err(e); } } /** * 游客登录:使用 deviceId 换取 guest token。 * 成功后写 guest token + deviceId + userId。 */ async guestLogin(deviceId: string): Promise> { return Result.wrap(async () => { const response = await this.api.guestLogin( GuestLoginRequest.from({ deviceId }), ); await this.storage.setGuestToken(response.token); await this.storage.setDeviceId(deviceId); if (response.userId) { await this.userStorage.setUserId(response.userId); } return response; }); } /** Google 登录。 */ async googleLogin(input: { idToken: string; guestId?: string; }): Promise> { return this._socialLogin(() => this.api.googleLogin( GoogleLoginRequest.from({ idToken: input.idToken, platform: WEB_PLATFORM, guestId: input.guestId ?? "", }), ), ); } /** Facebook 登录(accessToken 流程)。 */ async facebookLogin(input: { accessToken: string; guestId?: string; }): Promise> { return this._socialLogin(() => this.api.facebookLogin( FacebookLoginRequest.from({ accessToken: input.accessToken, platform: WEB_PLATFORM, guestId: input.guestId ?? "", }), ), ); } /** Facebook ID 登录(v7.0 新增,fbId 流程)。 */ async facebookIdLogin(input: { fbId: string; avatarUrl?: string; }): Promise> { return this._socialLogin(() => this.api.facebookIdLogin( FbIdLoginRequest.from({ fbId: input.fbId, avatarUrl: input.avatarUrl ?? "", }), ), ); } /** * Apple 登录。 * 注:Dart 端另有一个 `uploadFacebookAvatar` 方法,但当前 TS 端 `AuthApi` * 尚未实现该端点,故仓库暂不暴露。等 `AuthApi` 补齐后再加。 */ async appleLogin(identityToken: string): Promise> { return this._socialLogin(() => this.api.appleLogin( AppleLoginRequest.from({ identityToken, platform: WEB_PLATFORM, }), ), ); } /** * 刷新 token:先读本地的 refresh token,空则直接返回错误; * 调用 API 成功后写回新的 login token + refresh token。 */ async refreshToken(): Promise> { const existing = await this.storage.getRefreshToken(); if (!existing.success || !existing.data) { return Result.err(new Error("No refresh token available")); } const refreshToken = existing.data; return Result.wrap(async () => { const response = await this.api.refreshToken( RefreshTokenRequest.from({ refreshToken }), ); await this.storage.setLoginToken(response.token); if (response.refreshToken) { await this.storage.setRefreshToken(response.refreshToken); } return response; }); } /** * 获取当前登录用户。成功后**尽力**写本地 User 缓存(offline hydrate), * 缓存失败用 `console.warn` 记录,不影响主流程。 */ async getCurrentUser(): Promise> { return Result.wrap(async () => { const user = await this.api.getCurrentUser(); const writeResult = await this.userStorage.setUser(user.toJson()); if (!writeResult.success) { console.warn( "[AuthRepository] failed to cache current user", writeResult.error, ); } return user; }); } // ============ 私有助手 ============ /** * 社交登录的公共包装:调 API → 调 `_saveLoginData` 持久化。 * `_saveLoginData` 内部全部 best-effort,存储写失败不抛错。 */ private async _socialLogin( call: () => Promise, ): Promise> { return Result.wrap(async () => { const response = await call(); await this._saveLoginData(response); return response; }); } /** * 持久化登录态:login token → refresh token → User 对象 → userId。 * 全部 best-effort,错误用 `console.warn` 记录但不让登录「失败」—— * 调用方已经从 API 拿到了 LoginResponse,缓存只是离线加速。 */ private async _saveLoginData(data: LoginResponse): Promise { const r1 = await this.storage.setLoginToken(data.token); if (!r1.success) { console.warn("[AuthRepository] setLoginToken failed", r1.error); } if (data.refreshToken) { const r2 = await this.storage.setRefreshToken(data.refreshToken); if (!r2.success) { console.warn("[AuthRepository] setRefreshToken failed", r2.error); } } const r3 = await this.userStorage.setUser(data.user.toJson()); if (!r3.success) { console.warn("[AuthRepository] setUser failed", r3.error); } const r4 = await this.userStorage.setUserId(data.user.id); if (!r4.success) { console.warn("[AuthRepository] setUserId failed", r4.error); } } } /** 全局单例。 */ export const authRepository = new AuthRepository( authApi, AuthStorage.getInstance(), UserStorage.getInstance(), );