diff --git a/src/data/repositories/auth_repository.ts b/src/data/repositories/auth_repository.ts index 3e086ce2..171976ce 100644 --- a/src/data/repositories/auth_repository.ts +++ b/src/data/repositories/auth_repository.ts @@ -10,6 +10,8 @@ import { GuestLoginResponse, LoginRequest, LoginResponse, + LoginStatus, + type LoginStatus as LoginStatusT, RefreshTokenRequest, RefreshTokenResponse, RegisterRequest, @@ -79,7 +81,7 @@ export class AuthRepository implements IAuthRepository { }), ), ); - await this._saveLoginData(response); + await this._saveLoginData(response, LoginStatus.Email); return response; }); } @@ -131,6 +133,7 @@ export class AuthRepository implements IAuthRepository { userLastMessageAt: response.user?.lastMessageAt, }); await this.storage.setGuestToken(response.token); + await this.storage.setLoginProvider(LoginStatus.Guest); await this.storage.setDeviceId(deviceId); if (response.userId) { await this.userStorage.setUserId(response.userId); @@ -150,7 +153,7 @@ export class AuthRepository implements IAuthRepository { idToken: string; guestId?: string; }): Promise> { - return this._socialLogin(() => + return this._socialLogin(LoginStatus.Google, () => this.api.googleLogin( GoogleLoginRequest.from( withTestAccountFlag({ @@ -168,7 +171,7 @@ export class AuthRepository implements IAuthRepository { accessToken: string; guestId?: string; }): Promise> { - return this._socialLogin(() => + return this._socialLogin(LoginStatus.Facebook, () => this.api.facebookLogin( FacebookLoginRequest.from( withTestAccountFlag({ @@ -186,7 +189,7 @@ export class AuthRepository implements IAuthRepository { fbId: string; avatarUrl?: string; }): Promise> { - return this._socialLogin(() => + return this._socialLogin(LoginStatus.Facebook, () => this.api.facebookIdLogin( FbIdLoginRequest.from( withTestAccountFlag({ @@ -204,7 +207,7 @@ export class AuthRepository implements IAuthRepository { * 尚未实现该端点,故仓库暂不暴露。等 `AuthApi` 补齐后再加。 */ async appleLogin(identityToken: string): Promise> { - return this._socialLogin(() => + return this._socialLogin(LoginStatus.Apple, () => this.api.appleLogin( AppleLoginRequest.from( withTestAccountFlag({ @@ -263,11 +266,12 @@ export class AuthRepository implements IAuthRepository { * `_saveLoginData` 内部全部 best-effort,存储写失败不抛错。 */ private async _socialLogin( + provider: LoginStatusT, call: () => Promise, ): Promise> { return Result.wrap(async () => { const response = await call(); - await this._saveLoginData(response); + await this._saveLoginData(response, provider); return response; }); } @@ -277,11 +281,18 @@ export class AuthRepository implements IAuthRepository { * 全部 best-effort,错误用 `Logger.warn` 记录但不让登录「失败」—— * 调用方已经从 API 拿到了 LoginResponse,缓存只是离线加速。 */ - private async _saveLoginData(data: LoginResponse): Promise { + 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) { diff --git a/src/data/storage/auth/auth_storage.ts b/src/data/storage/auth/auth_storage.ts index 5b612dd6..a330dea0 100644 --- a/src/data/storage/auth/auth_storage.ts +++ b/src/data/storage/auth/auth_storage.ts @@ -11,9 +11,10 @@ * - 浏览器 → `localStorage` driver;SSR → memory driver(unstorage 自动) * - 单例挂在 class 静态字段,规避 HMR 复用污染 * - `hasXxx` = `token != null && token.length > 0`,对齐 Dart 端 `isNotEmpty` 语义 - * - `clearAuthData` 顺序清理:loginToken → guestToken → refreshToken,任一失败立即返回 + * - `clearAuthData` 顺序清理:loginToken → loginProvider → guestToken → refreshToken,任一失败立即返回 */ +import { LoginStatus, type LoginStatus as LoginStatusT } from "@/data/dto/auth"; import { Result, type Result as ResultT, SpAsyncUtil } from "@/utils"; import { StorageKeys } from "../storage_keys"; import type { IAuthStorage } from "./iauth_storage"; @@ -48,6 +49,22 @@ export class AuthStorage implements IAuthStorage { return SpAsyncUtil.remove(StorageKeys.loginToken); } + // ---- login provider ---- + + async getLoginProvider(): Promise> { + const r = await SpAsyncUtil.getString(StorageKeys.loginProvider); + if (!r.success) return Result.err(r.error); + if (r.data === null || r.data.length === 0) return Result.ok(null); + if (isLoginStatus(r.data)) return Result.ok(r.data); + return Result.ok(null); + } + setLoginProvider(provider: LoginStatusT): Promise> { + return SpAsyncUtil.setString(StorageKeys.loginProvider, provider); + } + clearLoginProvider(): Promise> { + return SpAsyncUtil.remove(StorageKeys.loginProvider); + } + // ---- guest token ---- getGuestToken(): Promise> { @@ -103,10 +120,16 @@ export class AuthStorage implements IAuthStorage { async clearAuthData(): Promise> { const r1 = await this.clearLoginToken(); if (!r1.success) return r1; - const r2 = await this.clearGuestToken(); + const r2 = await this.clearLoginProvider(); if (!r2.success) return r2; - const r3 = await this.clearRefreshToken(); + const r3 = await this.clearGuestToken(); if (!r3.success) return r3; + const r4 = await this.clearRefreshToken(); + if (!r4.success) return r4; return this.clearFacebookId(); } } + +function isLoginStatus(value: string): value is LoginStatusT { + return Object.values(LoginStatus).includes(value as LoginStatusT); +} diff --git a/src/data/storage/auth/iauth_storage.ts b/src/data/storage/auth/iauth_storage.ts index 11cae11d..da95fbc0 100644 --- a/src/data/storage/auth/iauth_storage.ts +++ b/src/data/storage/auth/iauth_storage.ts @@ -4,12 +4,13 @@ * IAuthStorage 接口 * * 对齐 Dart 端 `IAuthStorage`(lib/data/services/storage/iauth_storage.dart): - * - loginToken / guestToken / deviceId / refreshToken / facebookId 五个字段的 CRUD + * - loginToken / loginProvider / guestToken / deviceId / refreshToken / facebookId 字段的 CRUD * - `clearAuthData()` 批量清空登录态 * - 所有方法返回 `Promise>`,与 Dart `Future>` 对齐 */ import type { Result } from "@/utils"; +import type { LoginStatus } from "@/data/dto/auth"; export interface IAuthStorage { getLoginToken(): Promise>; @@ -17,6 +18,10 @@ export interface IAuthStorage { hasLoginToken(): Promise>; clearLoginToken(): Promise>; + getLoginProvider(): Promise>; + setLoginProvider(provider: LoginStatus): Promise>; + clearLoginProvider(): Promise>; + getGuestToken(): Promise>; setGuestToken(token: string): Promise>; hasGuestToken(): Promise>; diff --git a/src/data/storage/storage_keys.ts b/src/data/storage/storage_keys.ts index 1ccb9f67..3450d8da 100644 --- a/src/data/storage/storage_keys.ts +++ b/src/data/storage/storage_keys.ts @@ -9,6 +9,7 @@ export const StorageKeys = { // auth loginToken: "login_token", + loginProvider: "login_provider", guestToken: "guest_token", deviceId: "device_id", refreshToken: "refresh_token", diff --git a/src/stores/auth/auth-actors.ts b/src/stores/auth/auth-actors.ts index b189c58e..972509ec 100644 --- a/src/stores/auth/auth-actors.ts +++ b/src/stores/auth/auth-actors.ts @@ -235,7 +235,7 @@ export const guestLoginActor = fromPromise(async () => { // 1. deviceIdentifier.getDeviceId() —— 无则生成 + 落盘 // (生产环境使用 fingerprintjs;非生产环境使用本地 UUID) // (给后续 OAuth/email 登录准备 deviceId,不在 splash 首次访问就 auto-create 游客) -// 2. AuthStorage.getLoginToken() —— 有 → "email" +// 2. AuthStorage.getLoginToken() —— 有 → 读取 loginProvider,缺省回退 "email" // 3. AuthStorage.getFacebookId() —— 有 → facebookIdLogin → "facebook" // 4. AuthStorage.getGuestToken() —— 有 → "guest" // 5. 都没有 → "notLoggedIn"(让用户显式选 OAuth / Email / Guest 入口) @@ -248,6 +248,10 @@ export const checkAuthStatusActor = fromPromise(async () => { // 2. 查 loginToken const loginTokenR = await storage.getLoginToken(); if (loginTokenR.success && loginTokenR.data) { + const providerR = await storage.getLoginProvider(); + if (providerR.success && isBusinessLoginProvider(providerR.data)) { + return providerR.data; + } return "email" as LoginStatus; } @@ -281,3 +285,14 @@ export const checkAuthStatusActor = fromPromise(async () => { // 5. 都没有 → "notLoggedIn"(保持 splash 页面 + 登录入口) return "notLoggedIn" as LoginStatus; }); + +function isBusinessLoginProvider( + provider: LoginStatus | null, +): provider is LoginStatus { + return ( + provider === "email" || + provider === "facebook" || + provider === "google" || + provider === "apple" + ); +}