"use client"; /** * AuthStorage 完整实现(基于 unstorage 抽象) * * SECURITY: 当前与 Dart 端行为一致,refresh_token 以明文存于 localStorage。 * 后续轮次将升级到 HttpOnly Cookie 方案,参见 `clearAuthData` 处的 TODO 标记。 * * 设计说明: * - 内部使用 `SpAsyncUtil` 静态类(@/utils/storage) * - 浏览器 → `localStorage` driver;SSR → memory driver(unstorage 自动) * - 单例挂在 class 静态字段,规避 HMR 复用污染 * - `hasXxx` = `token != null && token.length > 0`,对齐 Dart 端 `isNotEmpty` 语义 * - `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"; export class AuthStorage implements IAuthStorage { private static _instance: AuthStorage | null = null; static getInstance(): AuthStorage { if (!AuthStorage._instance) AuthStorage._instance = new AuthStorage(); return AuthStorage._instance; } /** @internal */ static _resetForTests(): void { AuthStorage._instance = null; } // ---- login token ---- getLoginToken(): Promise> { return SpAsyncUtil.getString(StorageKeys.loginToken); } setLoginToken(token: string): Promise> { return SpAsyncUtil.setString(StorageKeys.loginToken, token); } async hasLoginToken(): Promise> { const r = await this.getLoginToken(); if (!r.success) return Result.err(r.error); return Result.ok(r.data !== null && r.data.length > 0); } clearLoginToken(): Promise> { 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> { return SpAsyncUtil.getString(StorageKeys.guestToken); } setGuestToken(token: string): Promise> { return SpAsyncUtil.setString(StorageKeys.guestToken, token); } async hasGuestToken(): Promise> { const r = await this.getGuestToken(); if (!r.success) return Result.err(r.error); return Result.ok(r.data !== null && r.data.length > 0); } clearGuestToken(): Promise> { return SpAsyncUtil.remove(StorageKeys.guestToken); } // ---- device id ---- getDeviceId(): Promise> { return SpAsyncUtil.getString(StorageKeys.deviceId); } setDeviceId(deviceId: string): Promise> { return SpAsyncUtil.setString(StorageKeys.deviceId, deviceId); } // ---- refresh token ---- getRefreshToken(): Promise> { return SpAsyncUtil.getString(StorageKeys.refreshToken); } setRefreshToken(token: string): Promise> { return SpAsyncUtil.setString(StorageKeys.refreshToken, token); } clearRefreshToken(): Promise> { return SpAsyncUtil.remove(StorageKeys.refreshToken); } // ---- facebook id ---- getFacebookId(): Promise> { return SpAsyncUtil.getString(StorageKeys.facebookId); } setFacebookId(id: string): Promise> { return SpAsyncUtil.setString(StorageKeys.facebookId, id); } clearFacebookId(): Promise> { return SpAsyncUtil.remove(StorageKeys.facebookId); } // ---- bulk clear ---- async clearAuthData(): Promise> { const r1 = await this.clearLoginToken(); if (!r1.success) return r1; const r2 = await this.clearLoginProvider(); if (!r2.success) return r2; 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); }