import type { EncryptionParams } from './cipher'; import { AesEncryption } from './cipher'; import { isNullOrUnDef } from './is'; import type { LockInfo, UserInfo } from '@dq-next/types/store'; import type { ProjectConfig } from '@dq-next/types/config'; import type { RouteLocationNormalized } from 'vue-router'; import { TOKEN_KEY, USER_INFO_KEY, ROLES_KEY, LOCK_INFO_KEY, PROJ_CFG_KEY, MULTIPLE_TABS_KEY, ADMINTOKEN_KEY, } from '@dq-next/types/enums/cacheEnum'; import { getAppEnvConfig, getEnv } from './env'; // System default cache time, in seconds export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7; // aes encryption key export const cacheCipher = { key: '_11111000001111@', iv: '@11111000001111_', }; export function getCommonStoragePrefix() { const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig(); return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}_`.toUpperCase(); } export interface CreateStorageParams extends EncryptionParams { prefixKey: string; storage: Storage | NodeMockStorage; hasEncrypt: boolean; timeout?: number; } class WebStorage { private storage: Storage | NodeMockStorage; private encryption: AesEncryption; private hasEncrypt: boolean; private prefixKey: string; private timeout: number; /** * * @param {*} storage */ constructor( storage: Storage, encryption: AesEncryption, hasEncrypt: boolean, prefixKey = '', timeout: number, ) { this.storage = storage; this.encryption = encryption; this.hasEncrypt = hasEncrypt; this.prefixKey = prefixKey; this.timeout = timeout; } private getKey(key: string) { return `${this.prefixKey}${key}`.toUpperCase(); } /** * Set cache * @param {string} key * @param {*} value * @param {*} expire Expiration time in seconds * @memberof Cache */ set(key: string, value: any, expire: number | null = this.timeout) { const stringData = JSON.stringify({ value, time: Date.now(), expire: !isNullOrUnDef(expire) ? new Date().getTime() + expire * 1000 : null, }); const stringifyValue = this.hasEncrypt ? this.encryption.encryptByAES(stringData) : stringData; this.storage.setItem(this.getKey(key), stringifyValue); } /** * Read cache * @param {string} key * @param {*} def * @memberof Cache */ get(key: string, def: any = null): any { const val = this.storage.getItem(this.getKey(key)); if (!val) return def; try { const decVal = this.hasEncrypt ? this.encryption.decryptByAES(val) : val; const data = JSON.parse(decVal); const { value, expire } = data; if (isNullOrUnDef(expire) || expire >= new Date().getTime()) { return value; } this.remove(key); } catch (e) { return def; } } /** * Delete cache based on key * @param {string} key * @memberof Cache */ remove(key: string) { this.storage.removeItem(this.getKey(key)); } /** * Delete all caches of this instance */ clear(): void { this.storage.clear(); } } export const createStorage = ({ prefixKey = getCommonStoragePrefix(), storage = sessionStorage, key = cacheCipher.key, iv = cacheCipher.iv, timeout = 7 * 24 * 60 * 60, hasEncrypt = false, }: Partial = {}) => { if (hasEncrypt && [key.length, iv.length].some((item) => item !== 16)) { throw new Error('When hasEncrypt is true, the key or iv must be 16 bits!'); } const encryption = new AesEncryption({ key, iv }); return new WebStorage(storage, encryption, hasEncrypt, prefixKey, timeout); }; type Options = Partial; export const createSessionStorage = (options: Options = {}) => { const storage = typeof window !== 'undefined' ? sessionStorage : new NodeMockStorage(); return createStorage({ ...options, timeout: DEFAULT_CACHE_TIME, storage: storage }); }; export const createLocalStorage = (options: Options = {}) => { const storage = typeof window !== 'undefined' ? localStorage : new NodeMockStorage(); return createStorage({ ...options, timeout: DEFAULT_CACHE_TIME, storage: storage }); }; interface BasicStore { [TOKEN_KEY]: string | number | null | undefined; [ADMINTOKEN_KEY]: string | number | null | undefined; [USER_INFO_KEY]: UserInfo; [ROLES_KEY]: string[]; [LOCK_INFO_KEY]: LockInfo; [PROJ_CFG_KEY]: ProjectConfig; [MULTIPLE_TABS_KEY]: RouteLocationNormalized[]; } export type BasicKeys = keyof BasicStore; class NodeMockStorage { private storage: Recordable = {}; constructor() {} getItem(key: string) { return this.storage[key]; } setItem(key: string, value: any) { this.storage[key] = value; } removeItem(key: string) { delete this.storage[key]; } clear() { this.storage = {}; } get length() { return Object.keys(this.storage).length; } key(index: number) { return Object.keys(this.storage)[index]; } } const ls = createLocalStorage(); const ss = createSessionStorage(); export const webStorage = { ls, ss, clearAll: () => { ls.clear(); ss.clear(); }, };