136 lines
4.6 KiB
TypeScript
136 lines
4.6 KiB
TypeScript
"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<ResultT<string | null>> {
|
||
return SpAsyncUtil.getString(StorageKeys.loginToken);
|
||
}
|
||
setLoginToken(token: string): Promise<ResultT<void>> {
|
||
return SpAsyncUtil.setString(StorageKeys.loginToken, token);
|
||
}
|
||
async hasLoginToken(): Promise<ResultT<boolean>> {
|
||
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<ResultT<void>> {
|
||
return SpAsyncUtil.remove(StorageKeys.loginToken);
|
||
}
|
||
|
||
// ---- login provider ----
|
||
|
||
async getLoginProvider(): Promise<ResultT<LoginStatusT | null>> {
|
||
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<ResultT<void>> {
|
||
return SpAsyncUtil.setString(StorageKeys.loginProvider, provider);
|
||
}
|
||
clearLoginProvider(): Promise<ResultT<void>> {
|
||
return SpAsyncUtil.remove(StorageKeys.loginProvider);
|
||
}
|
||
|
||
// ---- guest token ----
|
||
|
||
getGuestToken(): Promise<ResultT<string | null>> {
|
||
return SpAsyncUtil.getString(StorageKeys.guestToken);
|
||
}
|
||
setGuestToken(token: string): Promise<ResultT<void>> {
|
||
return SpAsyncUtil.setString(StorageKeys.guestToken, token);
|
||
}
|
||
async hasGuestToken(): Promise<ResultT<boolean>> {
|
||
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<ResultT<void>> {
|
||
return SpAsyncUtil.remove(StorageKeys.guestToken);
|
||
}
|
||
|
||
// ---- device id ----
|
||
|
||
getDeviceId(): Promise<ResultT<string | null>> {
|
||
return SpAsyncUtil.getString(StorageKeys.deviceId);
|
||
}
|
||
setDeviceId(deviceId: string): Promise<ResultT<void>> {
|
||
return SpAsyncUtil.setString(StorageKeys.deviceId, deviceId);
|
||
}
|
||
|
||
// ---- refresh token ----
|
||
|
||
getRefreshToken(): Promise<ResultT<string | null>> {
|
||
return SpAsyncUtil.getString(StorageKeys.refreshToken);
|
||
}
|
||
setRefreshToken(token: string): Promise<ResultT<void>> {
|
||
return SpAsyncUtil.setString(StorageKeys.refreshToken, token);
|
||
}
|
||
clearRefreshToken(): Promise<ResultT<void>> {
|
||
return SpAsyncUtil.remove(StorageKeys.refreshToken);
|
||
}
|
||
|
||
// ---- facebook id ----
|
||
|
||
getFacebookId(): Promise<ResultT<string | null>> {
|
||
return SpAsyncUtil.getString(StorageKeys.facebookId);
|
||
}
|
||
setFacebookId(id: string): Promise<ResultT<void>> {
|
||
return SpAsyncUtil.setString(StorageKeys.facebookId, id);
|
||
}
|
||
clearFacebookId(): Promise<ResultT<void>> {
|
||
return SpAsyncUtil.remove(StorageKeys.facebookId);
|
||
}
|
||
|
||
// ---- bulk clear ----
|
||
|
||
async clearAuthData(): Promise<ResultT<void>> {
|
||
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);
|
||
}
|