import { ExceptionHandler } from "@/core/errors"; import type { IAuthRepository } from "@/data/repositories/interfaces"; import { CheckoutHandoffConsumeRequestSchema, CheckoutHandoffCreateRequestSchema, type CheckoutHandoffCreateResponse, type CheckoutIntent, FacebookIdentityRequestSchema, FacebookLoginRequestSchema, FbIdLoginRequestSchema, GoogleLoginRequestSchema, GuestLoginRequestSchema, GuestLoginResponse, LoginRequestSchema, LoginResponse, LoginStatus, RefreshTokenRequestSchema, RefreshTokenResponse, RegisterRequestSchema, TopUpHandoffRequestSchema, type LoginStatus as LoginStatusT, } from "@/data/schemas/auth"; import { User } from "@/data/schemas/user"; import { ApiError, AuthApi, authApi, ErrorCode } from "@/data/services/api"; import { AuthStorage, type IAuthStorage } from "@/data/storage/auth"; import { UserStorage, type IUserStorage } from "@/data/storage/user"; import { AppEnvUtil } from "@/utils/app-env"; import { deviceIdentifier } from "@/utils/device_identifier"; import { Logger } from "@/utils/logger"; import { PlatformDetector } from "@/utils/platform-detect"; import { Result } from "@/utils/result"; 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( RegisterRequestSchema.parse( 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( LoginRequestSchema.parse( 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( GuestLoginRequestSchema.parse(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( GoogleLoginRequestSchema.parse( 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( FacebookLoginRequestSchema.parse( 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( FbIdLoginRequestSchema.parse( 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( FacebookIdentityRequestSchema.parse({ asid: input.asid ?? "", psid: input.psid ?? "", }), ); const data = response; await this._saveFacebookIdentity({ asid: data.fbAsid, psid: data.fbPsid, }); }); } /** 消费一次性充值凭证并按后端返回的 canonical 账号建立正式会话。 */ async consumeTopUpHandoff( handoffToken: string, ): Promise> { return Result.wrap(async () => { const response = await this.api.consumeTopUpHandoff( TopUpHandoffRequestSchema.parse({ handoffToken }), ); await this._saveLoginData(response, response.loginStatus); return response.loginStatus; }); } /** 创建订单前生成一次性外部浏览器支付交接链接。 */ async createCheckoutHandoff( checkoutIntent: CheckoutIntent, ): Promise> { return Result.wrap(() => this.api.createCheckoutHandoff( CheckoutHandoffCreateRequestSchema.parse(checkoutIntent), ), ); } /** 消费支付交接凭证,保存后端签发的正式登录态并返回购买意图。 */ async consumeCheckoutHandoff( handoffToken: string, ): Promise> { return Result.wrap(async () => { const response = await this.api.consumeCheckoutHandoff( CheckoutHandoffConsumeRequestSchema.parse({ handoffToken }), ); await this._saveLoginData(response, response.loginStatus); return response.checkoutIntent; }); } /** * 刷新 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( RefreshTokenRequestSchema.parse({ 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(); await this._cacheUser(user); return user; }); } // ============ 私有助手 ============ /** * 社交登录的公共包装:调 API → 调 `_saveLoginData` 持久化。 * 会话关键数据写入失败时,登录 Result 必须失败。 */ 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 → userId → provider。 * 这些字段共同决定会话是否可恢复,任一写入失败都会让登录 Result 失败; * provider 最后写入,作为会话完成标记。完整 User 对象仅用于离线展示, * 缓存失败只记录告警,不影响已经建立的会话。 */ private async _saveLoginData( data: LoginResponse, provider: LoginStatusT, ): Promise { const tokenResult = await this.storage.setLoginToken(data.token); if (Result.isErr(tokenResult)) throw tokenResult.error; if (data.refreshToken) { const refreshTokenResult = await this.storage.setRefreshToken( data.refreshToken, ); if (Result.isErr(refreshTokenResult)) throw refreshTokenResult.error; } const userIdResult = await this.userStorage.setUserId(data.user.id); if (Result.isErr(userIdResult)) throw userIdResult.error; const providerResult = await this.storage.setLoginProvider(provider); if (Result.isErr(providerResult)) throw providerResult.error; await this._cacheUser(data.user); } private async _cacheUser(user: User): Promise { try { const userResult = await this.userStorage.setUser(user); if (Result.isErr(userResult)) { log.warn("[AuthRepository] setUser failed", userResult.error); } } catch (error) { log.warn("[AuthRepository] setUser threw", 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(); }