Files
cozsweet-frontend-nextjs/src/data/storage/auth/auth_storage.ts
T

136 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
/**
* AuthStorage 完整实现(基于 unstorage 抽象)
*
* SECURITY: 当前与 Dart 端行为一致,refresh_token 以明文存于 localStorage。
* 后续轮次将升级到 HttpOnly Cookie 方案,参见 `clearAuthData` 处的 TODO 标记。
*
* 设计说明:
* - 内部使用 `SpAsyncUtil` 静态类(@/utils/storage
* - 浏览器 → `localStorage` driverSSR → memory driverunstorage 自动)
* - 单例挂在 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);
}