UNPKG

front-end-cache

Version:

支持typescript的前端浏览器缓存管理工具,browser front end cache (支持localstorage, sessionstorage, cookie)

208 lines (168 loc) 5.58 kB
export type FrontEndCacheType = 'localStorage' | 'sessionStorage' | 'cookie'; export type FrontEndCacheExpiresUnit = 'years' | 'months' | 'days' | 'hours' | 'minutes' | 'seconds'; export interface FrontEndCacheOptions<T = unknown> { /** * 仅仅 `type=cookie` * * @see {@link https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie} * * @default location.hostname */ domain: string; /** * 同 `cookie` 的 `path` 配置 * * @see {@link https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie} * * **注意和 原生 `cookie` 的区分,默认不是当前 `path`** * * @default / */ path: string; /** * 过期时间,默认单位 `seconds` ,可以通过 {@link SetItemOptions.expiresUnit} 配置 * * 针对 `sessionStorage` 和 `cookie` 默认值为当前会话 * * 针对 `localStorage` 默认值为 永久 */ expires?: number; /** * 过期时间单位 * * @default seconds */ expiresUnit: FrontEndCacheExpiresUnit; /** * 仅仅 `type=cookie` * * @default false */ secure: boolean; type: FrontEndCacheType; /** * 是否序通过 `JSON.stringify` 列化 `value` 数据 * * 如果为 `cookie` 还会通过 `encodeURIComponent` 一次 `JSON.stringify` 后的数据 * * **仅在 `value` 类型为 `string` 的时候可以禁用序列化**,但同样会调用 `encodeURIComponent` * * @default true */ serialize?: T extends string ? boolean : true; /** * 加密 `key` 的函数, * * 需要注意如果 `type = 'cookie'`, 最终的 `key` 也会 `encodeURIComponent` */ encryKey?: (key: string) => string; } export type FrontEndStorageValue<T> = [value: T, path: string, expires?: number]; const expiresUnitFuncMap = { years: 'FullYear', months: 'Month', days: 'Date', hours: 'Hours', minutes: 'Minutes', seconds: 'Seconds', } as const; export default class FrontEndCache<T = unknown> { private key: string; private options: FrontEndCacheOptions<T>; constructor (key: string, options: Partial<FrontEndCacheOptions<T>> = {}) { this.options = { domain: location.hostname, path: '/', expiresUnit: 'seconds', secure: false, type: 'localStorage', ...options, }; this.key = this.options.encryKey?.(key) || key; if (this.options.type === 'cookie') { this.key = encodeURIComponent(key); } } private get expiresTimeStamp (): number | null { if (typeof this.options.expires !== 'number') { return null; } const now = new Date(); const fun = expiresUnitFuncMap[this.options.expiresUnit]; // @ts-ignore now[`set${fun}`](now[`get${fun}`]() + this.options.expires); return now.getTime(); } private serializeValue (value: unknown): string { let _value; if (this.options.serialize === false) { _value = `${value}`; } else { _value = JSON.stringify(value); } /** `cookie` 始终进行一次 `encodeURIComponent` 避免特殊字符 */ if (this.options.type === 'cookie') { _value = encodeURIComponent(_value); } return _value; } private deserialization<V> (value?: string | null): V | null { try { let _value: string = value ?? ''; /** `cookie` 优先解码,然后直接返回 */ if (this.options.type === 'cookie') { _value = decodeURIComponent(_value); } return this.options.serialize === false ? _value : JSON.parse(_value); } catch { return null; } } private setCookie (value: T) { let cookie = `${this.key}=${this.serializeValue(value)};`; cookie += `path=${this.options.path};`; cookie += `domain=${this.options.domain};`; if (this.expiresTimeStamp) { cookie += `expires=${new Date(this.expiresTimeStamp).toUTCString()};`; } document.cookie = cookie; } private getCookie (): T | null { const value = document.cookie.replace(new RegExp(`(?:(?:^|.*;)\\s*${this.key.replace(/[-.+*]/g, '\\$&')}\\s*\\=\\s*([^;]*).*$)|^.*$`), '$1'); return this.deserialization<T>(value); } private removeCookie () { document.cookie = `${this.key}=; expires=Thu, 01 Jan 1970 00:00:00 GMT;domain=${this.options.domain};path=${this.options.path}`; } setItem (value: T): T { if (this.options.type === 'cookie') { this.setCookie(value); return value; } const _value: FrontEndStorageValue<T> = [value, this.options.path]; this.expiresTimeStamp && (_value[2] = this.expiresTimeStamp); window[this.options.type].setItem(this.key, this.serializeValue(_value)); return value; } getItem (): T | null getItem (defaultValue: T): T getItem (defaultValue?: T): T | null { let _value: T | null = null; if (this.options.type === 'cookie') { _value = this.getCookie(); } else { const res = this.deserialization<FrontEndStorageValue<T>>(window[this.options.type].getItem(this.key)); if (res && location.pathname.startsWith(res[1]) && (!res[2] || new Date(res[2]).getTime() - Date.now() >= 0)) { _value = res[0]; } } return _value ?? defaultValue ?? null; } removeItem () { if (this.options.type === 'cookie') { this.removeCookie(); } else { window[this.options.type].removeItem(this.key); } } }