front-end-cache
Version:
支持typescript的前端浏览器缓存管理工具,browser front end cache (支持localstorage, sessionstorage, cookie)
208 lines (168 loc) • 5.58 kB
text/typescript
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);
}
}
}