"use client"; import { ExceptionHandler } from "@/core/errors"; import { ApiError, AuthApi, authApi, ErrorCode } from "@/data/services/api"; import { FacebookIdentityRequest, FacebookPsidLoginRequest, FacebookLoginRequest, FbIdLoginRequest, GoogleLoginRequest, GuestLoginRequest, GuestLoginResponse, LoginRequest, LoginResponse, LoginStatus, type LoginStatus as LoginStatusT, RefreshTokenRequest, RefreshTokenResponse, RegisterRequest, } from "@/data/dto/auth"; import { User } from "@/data/dto/user"; import { AppEnvUtil, PlatformDetector, Result, Logger, deviceIdentifier, } from "@/utils"; import type { IAuthRepository } from "@/data/repositories/interfaces"; import { AuthStorage, type IAuthStorage } from "@/data/storage/auth"; import { UserStorage, type IUserStorage } from "@/data/storage/user"; import { createLazySingleton } from "./lazy_singleton"; const log = new Logger("DataRepositoriesAuthRepository"); const TEST_ACCOUNT_FLAG = "isTestAccount"; export class AuthRepository implements IAuthRepository { 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( withTestAccountFlag({ username: input.username, email: input.email, password: input.password, platform: getAuthPlatform(), guestId: input.guestId ?? "", }), ), ); }); } /** 邮箱/用户名 + 密码登录。 */ async emailLogin(input: { email?: string; username?: string; password: string; guestId?: string; psid?: string; }): Promise> { return Result.wrap(async () => { const email = input.email?.trim() ?? ""; const response = await this.api.emailLogin( LoginRequest.from( withTestAccountFlag({ email, username: input.username, password: input.password, platform: getAuthPlatform(), guestId: input.guestId ?? "", psid: input.psid ?? "", }), ), ); await this._saveLoginData(response, LoginStatus.Email); return response; }); } /** * 退出真实用户登录并恢复游客态。 * * 注意:业务上不允许从其他登录态退出到未登录态,所以这里不会删除 * guestToken / deviceId。退出后会尝试重新跑游客登录,以便恢复游客 userId; * 如果游客登录接口失败但本地仍有 guestToken,则降级回本地游客态,避免 * 用户从聊天页被打回 splash。 */ async logout(): Promise> { try { await this.api.logout(); } catch (e) { log.warn("[AuthRepository] logout API failed, restoring guest anyway", e); } try { const authClearResult = await this.storage.clearBusinessAuthData(); const userClearResult = await this.userStorage.clearUserData(); if (Result.isErr(authClearResult)) return authClearResult; if (Result.isErr(userClearResult)) return userClearResult; } catch (e) { return Result.err(e); } try { const deviceId = await deviceIdentifier.getDeviceId(); const guestResult = await this.guestLogin(deviceId); if (Result.isOk(guestResult)) return Result.ok(undefined); const hasGuestToken = await this.storage.hasGuestToken(); if (Result.isErr(hasGuestToken)) return hasGuestToken; if (hasGuestToken.data) { const providerResult = await this.storage.setLoginProvider( LoginStatus.Guest, ); if (Result.isErr(providerResult)) return providerResult; log.warn( "[AuthRepository] guest login failed after logout, falling back to local guest token", guestResult.error, ); return Result.ok(undefined); } return Result.err(guestResult.error); } catch (e) { return Result.err(e); } } /** * 游客登录:使用 deviceId 换取 guest token。 * 成功后写 guest token + deviceId + userId。 */ async guestLogin(deviceId: string): Promise> { log.debug("[AuthRepository.guestLogin] API call START", { deviceIdLength: deviceId.length, deviceIdPrefix: deviceId.slice(0, 8), }); return Result.wrap(async () => { const response = await this.api.guestLogin( GuestLoginRequest.from(withTestAccountFlag({ deviceId })), ); log.debug("[AuthRepository.guestLogin] API call SUCCESS (Zod parsed)", { hasToken: !!response.token, hasUser: !!response.user, userLastMessageAt: response.user?.lastMessageAt, }); const tokenResult = await this.storage.setGuestToken(response.token); if (Result.isErr(tokenResult)) throw tokenResult.error; const providerResult = await this.storage.setLoginProvider( LoginStatus.Guest, ); if (Result.isErr(providerResult)) throw providerResult.error; const deviceResult = await this.storage.setDeviceId(deviceId); if (Result.isErr(deviceResult)) throw deviceResult.error; if (response.userId) { const userIdResult = await this.userStorage.setUserId(response.userId); if (Result.isErr(userIdResult)) throw userIdResult.error; } return response; }).catch((e) => { const errorResult = ExceptionHandler.handle(e); log.error("[AuthRepository.guestLogin] API call FAILED", { errorCode: errorResult.code, errorMessage: errorResult.message, }); return Result.err(e); }); } /** Google 登录。 */ async googleLogin(input: { idToken: string; guestId?: string; psid?: string; }): Promise> { return this._socialLogin(LoginStatus.Google, () => this.api.googleLogin( GoogleLoginRequest.from( withTestAccountFlag({ idToken: input.idToken, platform: getAuthPlatform(), guestId: input.guestId ?? "", psid: input.psid ?? "", }), ), ), ); } /** Facebook 登录(accessToken 流程)。 */ async facebookLogin(input: { accessToken: string; guestId?: string; psid?: string; }): Promise> { return this._socialLogin(LoginStatus.Facebook, () => this.api.facebookLogin( FacebookLoginRequest.from( withTestAccountFlag({ accessToken: input.accessToken, platform: getAuthPlatform(), guestId: input.guestId ?? "", psid: input.psid ?? "", }), ), ), ); } /** 通过 ASID 登录。 */ async facebookAsidLogin(input: { asid: string; avatarUrl?: string; psid?: string; }): Promise> { return this._socialLogin(LoginStatus.Facebook, () => this.api.facebookIdLogin( FbIdLoginRequest.from( withTestAccountFlag({ fbId: input.asid, avatarUrl: input.avatarUrl ?? "", psid: input.psid ?? "", }), ), ), ); } /** 绑定 Facebook ASID / PSID 到当前登录用户。 */ async bindFacebookIdentity(input: { asid?: string; psid?: string; }): Promise> { if (!input.asid && !input.psid) return Result.ok(undefined); return Result.wrap(async () => { const response = await this.api.bindFacebookIdentity( FacebookIdentityRequest.from({ asid: input.asid ?? "", psid: input.psid ?? "", }), ); const data = response.toJson(); await this._saveFacebookIdentity({ asid: data.fbAsid, psid: data.fbPsid, }); }); } /** 通过 Facebook PSID 直接登录或返回游客态。 */ async facebookPsidLogin(input: { psid: string; deviceId?: string; bindToGuest?: boolean; }): Promise> { return Result.wrap(async () => { const response = await this.api.facebookPsidLogin( FacebookPsidLoginRequest.from({ psid: input.psid, deviceId: input.deviceId ?? "", bindToGuest: input.bindToGuest ?? true, }), ); const data = response.toJson(); await this._saveFacebookIdentity({ asid: data.fbAsid, psid: data.fbPsid, }); if (data.isGuest || !data.hasCompleteFacebookIdentity) { await this.storage.setGuestToken(data.token); await this.storage.setLoginProvider(LoginStatus.Guest); if (input.deviceId) { await this.storage.setDeviceId(input.deviceId); } const userId = data.userId || data.user?.id || ""; if (userId) { await this.userStorage.setUserId(userId); } return LoginStatus.Guest; } await this._saveLoginData( LoginResponse.from({ token: data.token, refreshToken: data.refreshToken, user: data.user ?? { id: data.userId }, }), LoginStatus.Facebook, ); return LoginStatus.Facebook; }); } /** * 刷新 token:先读本地的 refresh token,空则直接返回错误; * 调用 API 成功后写回新的 login token + refresh token。 */ async refreshToken(): Promise> { return Result.wrap(async () => { const existing = await this.storage.getRefreshToken(); if (Result.isErr(existing)) throw existing.error; if (!existing.data) { throw new ApiError( "HTTP_UNAUTHORIZED", "No refresh token available", ErrorCode.httpUnauthorized, ); } const response = await this.api.refreshToken( RefreshTokenRequest.from({ refreshToken: existing.data }), ); const tokenResult = await this.storage.setLoginToken(response.token); if (Result.isErr(tokenResult)) throw tokenResult.error; if (response.refreshToken) { const refreshTokenResult = await this.storage.setRefreshToken( response.refreshToken, ); if (Result.isErr(refreshTokenResult)) throw refreshTokenResult.error; } return response; }); } /** * 获取当前登录用户。成功后尽力写本地 User 缓存(offline hydrate), * 缓存失败用 `Logger.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) { log.warn( "[AuthRepository] failed to cache current user", writeResult.error, ); } return user; }); } // ============ 私有助手 ============ /** * 社交登录的公共包装:调 API → 调 `_saveLoginData` 持久化。 * `_saveLoginData` 内部全部 best-effort,存储写失败不抛错。 */ private async _socialLogin( provider: LoginStatusT, call: () => Promise, ): Promise> { return Result.wrap(async () => { const response = await call(); await this._saveLoginData(response, provider); return response; }); } /** * 持久化登录态:login token → refresh token → User 对象 → userId。 * 全部 best-effort,错误用 `Logger.warn` 记录但不让登录「失败」—— * 调用方已经从 API 拿到了 LoginResponse,缓存只是离线加速。 */ private async _saveLoginData( data: LoginResponse, provider: LoginStatusT, ): Promise { const r1 = await this.storage.setLoginToken(data.token); if (!r1.success) { log.warn("[AuthRepository] setLoginToken failed", r1.error); } const providerResult = await this.storage.setLoginProvider(provider); if (!providerResult.success) { log.warn("[AuthRepository] setLoginProvider failed", providerResult.error); } if (data.refreshToken) { const r2 = await this.storage.setRefreshToken(data.refreshToken); if (!r2.success) { log.warn("[AuthRepository] setRefreshToken failed", r2.error); } } const r3 = await this.userStorage.setUser(data.user.toJson()); if (!r3.success) { log.warn("[AuthRepository] setUser failed", r3.error); } const r4 = await this.userStorage.setUserId(data.user.id); if (!r4.success) { log.warn("[AuthRepository] setUserId failed", r4.error); } } private async _saveFacebookIdentity(input: { asid?: string; psid?: string; }): Promise { if (input.asid) { const r = await this.storage.setAsid(input.asid); if (!r.success) { log.warn("[AuthRepository] setAsid failed", r.error); } } if (input.psid) { const r = await this.storage.setPsid(input.psid); if (!r.success) { log.warn("[AuthRepository] setPsid failed", r.error); } } } } /** 全局懒单例。 */ export const getAuthRepository = createLazySingleton( () => new AuthRepository( authApi, AuthStorage.getInstance(), UserStorage.getInstance(), ), ); function withTestAccountFlag>( payload: T, ): T & { isTestAccount: boolean } { return { ...payload, [TEST_ACCOUNT_FLAG]: !AppEnvUtil.isProduction(), }; } function getAuthPlatform(): string { return PlatformDetector.getPlatform(); }