Files
opsxsql/frontend/src/lib/storage.ts
T
Logan.Li d5b8b8116c fix(frontend): harden auth request layer per web guide
- fix double-unwrap bug in useLogin prefs sync (theme sync silently broken)
- request layer: 401 single-flight refresh queue + debounced single redirect
  with return-url, cancel-aware silent passthrough, skipErrorHandler opt-out,
  typed blob download (replace bare fetch export in QueriesPage)
- useFetchList: AbortController + version guard + error state (three states)
- ErrorBoundary with chunk-load auto recovery (reload once)
- permission single source: derive route guard from menu constants, drop
  hand-maintained routePermissionMap; TicketDetailPage uses unified
  hasActionPermission instead of inline role string comparison
- namespaced localStorage keys (opsxsql.*) with read-old-write-new migration
- fix pre-existing tsc errors (InstancePage/WorkflowPage/QueriesPage/
  PlatformConfigPage/RedisDetailPage) - typecheck now passes clean
2026-10-26 17:12:29 +08:00

80 lines
2.2 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.
/**
* 统一的 localStorage 命名空间与迁移层。
*
* 所有持久化 key 一律从 STORAGE_KEYS 取值(`<app>.<域>.<名>` 命名空间),
* 禁止在业务代码里手写字符串 key。历史无命名空间 key 仅在此处做
* 「读旧写新」迁移,迁移期结束后删除 LEGACY_KEYS。
*/
const read = (key: string): string | null => {
try {
return localStorage.getItem(key);
} catch {
return null;
}
};
const write = (key: string, value: string): void => {
try {
localStorage.setItem(key, value);
} catch {
/* 存储不可用(隐私模式等)时静默降级为内存态 */
}
};
const remove = (key: string): void => {
try {
localStorage.removeItem(key);
} catch {
/* noop */
}
};
export const STORAGE_KEYS = {
token: 'opsxsql.auth.token',
refreshToken: 'opsxsql.auth.refresh',
theme: 'opsxsql.theme.mode',
loginStyle: 'opsxsql.ui.loginStyle',
queryFavorites: 'opsxsql.query.favorites',
querySideWidth: 'opsxsql.query.sideWidth',
chunkReloaded: 'opsxsql.app.chunkReloaded',
} as const;
/** 历史无命名空间 key → 新 key(仅用于一次性迁移读取) */
const LEGACY_KEYS: Partial<Record<keyof typeof STORAGE_KEYS, string>> = {
token: 'token',
theme: 'dg-theme',
loginStyle: 'login-style',
queryFavorites: 'query_favorites',
querySideWidth: 'query-side-width',
};
/** 读取(新 key 优先,回退旧 key 并完成迁移写入) */
export function readStorage(key: keyof typeof STORAGE_KEYS): string | null {
const current = read(STORAGE_KEYS[key]);
if (current !== null) return current;
const legacyKey = LEGACY_KEYS[key];
if (legacyKey) {
const legacy = read(legacyKey);
if (legacy !== null) {
write(STORAGE_KEYS[key], legacy);
remove(legacyKey);
return legacy;
}
}
return null;
}
/** 写入(只写新 key) */
export function writeStorage(key: keyof typeof STORAGE_KEYS, value: string): void {
write(STORAGE_KEYS[key], value);
}
/** 删除(新旧 key 一起清理) */
export function removeStorage(key: keyof typeof STORAGE_KEYS): void {
remove(STORAGE_KEYS[key]);
const legacyKey = LEGACY_KEYS[key];
if (legacyKey) remove(legacyKey);
}