aura-storage
Version:
764 lines (752 loc) • 24.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var aes = require('crypto-js/aes');
var UTF8 = require('crypto-js/enc-utf8');
var pkcs7 = require('crypto-js/pad-pkcs7');
var ECB = require('crypto-js/mode-ecb');
var localforage = require('localforage');
var auraShared = require('aura-shared');
var auraCore = require('aura-core');
var dayjs = require('dayjs');
const cacheConfig = {
enableStorageEncryption: true,
DEFAULT_CACHE_TIME: 3e3,
prefixKey: ""
};
const cacheCipher = {
key: "_11111000001111@",
iv: "@11111000001111_"
};
var StorageType = /* @__PURE__ */ ((StorageType2) => {
StorageType2["INDEXEDDB"] = "INDEXEDDB";
StorageType2["WEBSTORAGE"] = "WEBSTORAGE";
return StorageType2;
})(StorageType || {});
class AesEncryption {
constructor(opt) {
const { key, iv } = opt;
if (key) {
this.key = UTF8.parse(key);
}
if (iv) {
this.iv = UTF8.parse(iv);
}
}
get getOptions() {
return {
mode: ECB,
padding: pkcs7,
iv: this.iv
};
}
encryptByAES(cipherText) {
return aes.encrypt(cipherText, this.key, this.getOptions).toString();
}
decryptByAES(cipherText) {
return aes.decrypt(cipherText, this.key, this.getOptions).toString(UTF8);
}
}
class StorageHelper {
constructor({
hasEncrypt = true,
encryption,
onExpire,
cacheSize = 1e3
}) {
this.cache = new auraCore.AlgorithmUtils.LRUCache(cacheSize);
this.hasEncrypt = hasEncrypt;
this.encryption = encryption;
this.onExpire = onExpire;
this.cacheSize = cacheSize;
}
/**
* 生成带有前缀(如果有)并转换为大写的键。
*
*
* @param {string} key - 需要格式化的原始键。
* @returns {string} - 带有前缀并转换为大写的键。
*/
getKey(key, prefixKey) {
return `${prefixKey ? `${prefixKey}_` : ""}${key}`.toUpperCase();
}
/**
* 检查数据是否未过期。
*
* @param {any} data - 数据对象。
* @returns {boolean} - 是否未过期。
*/
isNotExpired(data) {
console.log("\u{1F680} ~ StorageHelper ~ isNotExpired ~ data:", data);
return auraShared.isUndefinedOrNull(data.expire) || data.expire >= Date.now();
}
// 处理缓存中的数据
handleCachedEntry(key, cachedEntry, def = null) {
if (this.isNotExpired(cachedEntry.data)) {
return cachedEntry.data.value;
} else {
this.cache.delete(key);
if (this.onExpire) {
this.onExpire(key, cachedEntry.data);
}
return def;
}
}
/**
* 异步加密数据(如果启用了加密)。
*
* @param {string} data - 要加密的数据。
* @returns {Promise<string>} - 加密后的数据。
*/
encryptData(data) {
try {
return this.hasEncrypt ? this.encryption.encryptByAES(data) : data;
} catch (error) {
this._handleError("\u52A0\u5BC6\u6570\u636E\u5931\u8D25", error);
throw error;
}
}
/**
* 解密数据(如果启用了解密数据)。
*
* @param {string} encryptedData - 要解密的数据。
* @returns {Promise<string>} -解密后的数据。
*/
decryptData(encryptedData) {
try {
return this.hasEncrypt ? this.encryption.decryptByAES(encryptedData) : encryptedData;
} catch (error) {
this._handleError("\u52A0\u5BC6\u6570\u636E\u5931\u8D25", error);
throw error;
}
}
// 设置缓存
setCache(key, value) {
this.cache.set(key, value);
}
// 从缓存中获取数据
getCache(key) {
return this.cache.get(key);
}
// 从缓存中删除数据
deleteCache(key) {
this.cache.delete(key);
}
// 清除所有缓存
clearCache() {
this.cache.clear();
}
/**
* 准备要存储的数据。
*
* @param {*} value - 要存储的数据。
* @param {number} [expire=0] - 过期时间(单位`分`,默认`0`分钟,永久缓存)。
* @returns {string} - 准备好的 JSON 字符串。
*/
prepareData(value, expire = null) {
!auraShared.isUndefinedOrNull(expire) ? (/* @__PURE__ */ new Date()).getTime() + expire * 60 * 1e3 : null;
if (this._calculateExpireTime(expire)) ;
return JSON.stringify({
value,
time: Date.now(),
expire: this._calculateExpireTime(expire),
// 单位转换为毫秒
isEncrypted: true,
expireTime: this._expireTime(expire),
cacheTime: dayjs(Date.now()).format("YYYY-MM-DD HH:mm:ss")
});
}
/**
* 处理错误并记录日志。
*
* @param {string} message - 错误信息。
* @param {any} error - 错误对象。
*/
_handleError(message, error) {
console.error(message, error);
throw new Error(message);
}
_calculateExpireTime(expireMinutes) {
if (auraShared.isUndefinedOrNull(expireMinutes)) {
return null;
}
return (/* @__PURE__ */ new Date()).getTime() + expireMinutes * 60 * 1e3;
}
_expireTime(expireMinutes) {
if (auraShared.isUndefinedOrNull(expireMinutes)) {
return null;
}
return dayjs((/* @__PURE__ */ new Date()).getTime() + expireMinutes * 60 * 1e3).format("YYYY-MM-DD HH:mm:ss");
}
}
class IndexedDBStorage {
constructor({ prefixKey, hasEncrypt = true, encryption, onExpire, cacheName }) {
this.cacheName = cacheName;
this.storage = localforage.createInstance({
driver: [localforage.INDEXEDDB, localforage.LOCALSTORAGE],
name: cacheName
// 可以考虑使用更具有描述性的名称
});
this._prefixKey = prefixKey;
this._helper = new StorageHelper({ hasEncrypt, encryption, onExpire });
this._init();
}
/**
* 异步设置缓存项,并可以选择设置过期时间。
*
* @param {string} key - 要存储数据的键。
* @param {*} value - 要存储的数据。
* @param {number} [expire] - 过期时间(单位`分`,默认`0`分钟,永久缓存)。
* @returns {Promise<void>} - 一个 Promise 对象,表示设置操作的结果。
*/
async setItemAsync(key, value, expire = null) {
try {
const data = this._helper.prepareData(value, expire);
const encryptedData = this._helper.encryptData(data);
await this.storage.setItem(this._helper.getKey(key, this._prefixKey), encryptedData);
this._helper.deleteCache(key);
} catch (error) {
console.error("Error setting item in storage:", error);
throw error;
}
}
/**
* 异步获取缓存项。
*
* @param {string} key - 要获取的键。
* @param {*} [def=null] - 如果键不存在或已过期,默认返回的值。
* @returns {Promise<T | null>} - 一个 Promise 对象,表示获取操作的结果。
*/
async getItemAsync(key, def = null) {
const cachedEntry = this._helper.getCache(key);
if (cachedEntry) {
console.log("=========\u8D70\u7684\u7F13\u5B58");
return this._helper.handleCachedEntry(key, cachedEntry, def);
}
try {
const res = await this.storage.getItem(this._helper.getKey(key, this._prefixKey));
if (!res)
return def;
const decryptedData = this._helper.decryptData(res);
let parsedData;
try {
parsedData = JSON.parse(decryptedData);
} catch (parseError) {
console.error(`Error parsing data for key ${key}:`, parseError);
return def;
}
if (this._helper.isNotExpired(parsedData)) {
this._helper.setCache(key, { data: parsedData });
return parsedData.value;
} else {
await this.remove(key);
return def;
}
} catch (error) {
console.error("Error getting item from storage:", error);
return def;
}
}
/**
* 异步从离线仓库中删除对应键名的值。
*
* @param {string} key - 键名。
* @returns {Promise<boolean>} - 操作是否成功。
*/
async remove(key) {
try {
await this.storage.removeItem(this._helper.getKey(key, this._prefixKey));
this._helper.deleteCache(key);
return true;
} catch (error) {
console.error("Error removing item from storage:", error);
return false;
}
}
/**
* 异步从离线仓库中删除所有的键名,重置数据库。
*
* @returns {Promise<boolean>} - 操作是否成功。
*/
async clear() {
try {
await this.storage.clear();
this._helper.clearCache();
return true;
} catch (error) {
console.error("Error clearing storage:", error);
return false;
}
}
/**
* 异步获取数据仓库中所有的key。
*
* @returns {Promise<string[]>} - 一个 Promise 对象,表示获取所有键的结果。
*/
async allKeys() {
try {
return await this.storage.keys();
} catch (error) {
console.error("Error fetching keys from storage:", error);
throw new Error("Failed to fetch keys from storage");
}
}
/**
* 异步初始化数据。
*
* @returns {Promise<void>} - 一个 Promise 对象,表示初始化的结果。
*/
async _init() {
try {
const keys = await this.storage.keys();
for (const key of keys) {
if (!key)
continue;
if (this._prefixKey && !key.startsWith(`${this._prefixKey}_`))
continue;
try {
const item = await this.storage.getItem(key);
if (!item)
continue;
const decryptedData = this._helper.decryptData(item);
let parsedData;
try {
parsedData = JSON.parse(decryptedData);
} catch (parseError) {
console.error(`Error parsing data for key ${key}:`, parseError);
continue;
}
if (this._helper.isNotExpired(parsedData)) {
this._helper.setCache(key, { data: parsedData });
} else {
await this.remove(key);
}
} catch (error) {
console.error(`Error initializing item ${key}:`, error);
}
}
} catch (error) {
console.error("Error fetching keys from storage:", error);
}
}
}
class StorageAdapter {
// 新增的方法,将所有localStorage的其他方法代理到这个类
constructor(storage) {
this.storage = storage;
}
/**
* 同步获取项。
*
* @param {string} key - 要获取的键。
* @returns {string | null} - 存储的值或 null。
*/
getItemSync(key) {
return this.storage.getItem(key);
}
/**
* 异步获取项。
*
* @param {string} key - 要获取的键。
* @returns {Promise<string | null>} - 一个 Promise 对象,表示获取操作的结果。
*/
getItemAsync(key) {
return new Promise((resolve) => {
resolve(this.storage.getItem(key));
});
}
/**
* 同步设置项。
*
* @param {string} key - 要设置的键。
* @param {string} value - 要存储的值。
*/
setItemSync(key, value) {
this.storage.setItem(key, value);
}
/**
* 异步设置项。
*
* @param {string} key - 要设置的键。
* @param {string} value - 要存储的值。
* @returns {Promise<void>} - 一个 Promise 对象,表示设置操作的结果。
*/
setItemAsync(key, value) {
return new Promise((resolve, reject) => {
try {
this.storage.setItem(key, value);
resolve();
} catch (error) {
reject(error);
}
});
}
}
class WebStorage {
/**
* 创建一个 WebStorage 实例,并初始化缓存。
*
* @param {Storage} storage - 使用的存储对象(sessionStorage 或 localStorage)。
* @param {string} [prefixKey] - 可选的键前缀。用于命名空间划分。
* @param {boolean} [hasEncrypt=true] - 是否对存储的数据进行 AES 加密。
* @param {AesEncryption} [encryption] - AES 加密工具实例。如果不提供,则创建一个新的实例。
*/
constructor({ storage, prefixKey, hasEncrypt = true, encryption, cacheSize = 1e3, onExpire }) {
this._prefixKey = prefixKey;
this.hasEncrypt = hasEncrypt;
this._adapter = new StorageAdapter(storage);
this._helper = new StorageHelper({ hasEncrypt, encryption, onExpire, cacheSize });
this._initializeCache();
}
/**
* 同步设置缓存项,并可以选择设置过期时间。
*
* @param {string} key - 要存储数据的键。
* @param {*} value - 要存储的数据。
* @param {number | null} [expire=null] - 过期时间(以秒为单位)。如果为 null,则数据不会过期。
* @throws {Error} 如果在设置存储项时发生错误,则抛出异常。
*/
setItemSync(key, value, expire = null) {
const formattedKey = this._helper.getKey(key, this._prefixKey);
const data = this._helper.prepareData(value, expire);
const encryptedData = this._helper.encryptData(data);
try {
this._adapter.storage.setItem(formattedKey, encryptedData);
this._setCache(key, formattedKey, data);
} catch (error) {
this._helper._handleError("\u8BBE\u7F6E\u5B58\u50A8\u9879\u5931\u8D25", error);
}
}
/**
* 异步设置缓存项,并可以选择设置过期时间。
*
* @param {string} key - 要存储数据的键。
* @param {*} value - 要存储的数据。
* @param {number | null} [expire=null] - 过期时间(以秒为单位)。如果为 null,则数据不会过期。
* @returns {Promise<void>} - 一个 Promise 对象,表示设置操作的结果。
*/
async setItemAsync(key, value, expire = null) {
const formattedKey = this._helper.getKey(key, this._prefixKey);
const data = this._helper.prepareData(value, expire);
const encryptedData = this._helper.encryptData(data);
try {
this._adapter.storage.setItem(formattedKey, encryptedData);
this._setCache(key, formattedKey, data);
} catch (error) {
this._helper._handleError("\u8BBE\u7F6E\u5B58\u50A8\u9879\u5931\u8D25", error);
}
}
/**
* 同步获取缓存项。
*
* @param {string} key - 要获取的键。
* @param {*} [def=null] - 如果键不存在或已过期,默认返回的值。
* @returns {T | null} - 缓存的值或默认值。
*/
getItemSync(key, def = null) {
const cachedEntry = this._getCacheEntry(key);
if (cachedEntry) {
console.log("\u540C\u6B65\u65B9\u6CD5--------\u8D70\u7684\u7F13\u5B58");
return this._handleCachedEntrySync(cachedEntry, def);
}
const storedKey = this._helper.getKey(key, this._prefixKey);
const val = this._adapter.getItemSync(storedKey);
if (!val)
return def;
try {
const decVal = this._helper.decryptData(val);
const data = JSON.parse(decVal);
if (this._helper.isNotExpired(data)) {
this._setCache(key, storedKey, decVal);
return data.value;
} else {
this.remove(key);
return def;
}
} catch (error) {
this._helper._handleError("\u83B7\u53D6\u5B58\u50A8\u9879\u5931\u8D25", error);
return def;
}
}
/**
* 异步获取缓存项。
*
* @param {string} key - 要获取的键。
* @param {*} [def=null] - 如果键不存在或已过期,默认返回的值。
* @returns {Promise<T | null>} - 缓存的值或默认值。
*/
async getItemAsync(key, def = null) {
const cachedEntry = this._getCacheEntry(key);
if (cachedEntry) {
return this._handleCachedEntry(cachedEntry, def);
}
const storedKey = this._helper.getKey(key, this._prefixKey);
const val = this._adapter.storage.getItem(storedKey);
if (!val)
return def;
try {
const data = await this._parseAndCacheData(storedKey, val);
return data.value;
} catch (error) {
this._helper._handleError("\u83B7\u53D6\u5B58\u50A8\u9879\u5931\u8D25", error);
return def;
}
}
/**
* 根据键删除缓存项。
*
* @param {string} key - 要删除的键。
* @throws {Error} 如果在删除存储项时发生错误,则抛出异常。
*/
remove(key) {
try {
const formattedKey = this._helper.getKey(key, this._prefixKey);
this._adapter.storage.removeItem(formattedKey);
this._helper.deleteCache(key);
} catch (error) {
this._helper._handleError("\u5220\u9664\u5B58\u50A8\u9879\u5931\u8D25", error);
}
}
/**
* 清除所有与此实例相关的缓存。
*
* 如果指定了前缀,则只清除带有该前缀的项;否则清除所有项。
*
* @throws {Error} 如果在清除存储时发生错误,则抛出异常。
*/
clear() {
try {
if (this._prefixKey) {
this._clearWithPrefix();
} else {
this._adapter.storage.clear();
}
this._helper.clearCache();
} catch (error) {
this._helper._handleError("\u6E05\u9664\u5B58\u50A8\u5931\u8D25", error);
}
}
/**
* 获取所有与此实例相关的键。
*
* 如果指定了前缀,则只返回带有该前缀的键;否则返回所有键。
*
* @returns {string[]} - 包含所有键的数组。
*/
allKeys() {
const keys = [];
const prefixPattern = this._prefixKey ? new RegExp(`^${this._prefixKey}_`, "i") : null;
for (let i = 0; i < this._adapter.storage.length; i++) {
const key = this._adapter.storage.key(i);
if (key && (!prefixPattern || prefixPattern.test(key))) {
keys.push(key);
}
}
return keys;
}
/**
* 初始化缓存,加载存储中的现有数据。
*
* @private
*/
_initializeCache() {
try {
for (let i = 0; i < this._adapter.storage.length; i++) {
const storedKey = this._adapter.storage.key(i);
if (!storedKey)
continue;
if (this._prefixKey && !storedKey.startsWith(`${this._prefixKey}_`.toUpperCase())) {
continue;
}
const val = this._adapter.storage.getItem(storedKey);
if (!val)
continue;
try {
const decVal = this._helper.decryptData(val);
const originalKey = storedKey.replace(new RegExp(`^${this._prefixKey}_`, "i"), "").toLowerCase();
this._setCache(originalKey, storedKey, decVal);
} catch (error) {
console.warn(`\u521D\u59CB\u5316\u7F13\u5B58\u65F6\u89E3\u6790\u952E ${storedKey} \u5931\u8D25`, error);
}
}
} catch (error) {
this._helper._handleError("\u521D\u59CB\u5316\u7F13\u5B58\u5931\u8D25", error);
}
}
// 计算大小
get size() {
let totalSize = 0;
for (let i = 0; i < this._adapter.storage.length; i++) {
const key = this._adapter.storage.key(i);
const value = this._adapter.storage.getItem(key);
totalSize += (key.length + value.length) * 2;
}
const sizeInKB = (totalSize / 1024).toFixed(2) * 1;
return sizeInKB;
}
/**
* 处理缓存条目,返回解析后的数据或默认值(同步版本)。
*
* @param {{ formattedKey: string, rawData: string }} cachedEntry - 缓存条目。
* @param {*} [def=null] - 默认返回的值。
* @returns {T | null} - 解析后的数据或默认值。
*/
_handleCachedEntrySync(cachedEntry, def = null) {
try {
const data = JSON.parse(cachedEntry.rawData);
if (this._helper.isNotExpired(data)) {
const parsedData = data.value;
this._setCache(cachedEntry.formattedKey, cachedEntry.formattedKey, Promise.resolve(parsedData));
return parsedData;
} else {
this.remove(cachedEntry.formattedKey);
return def;
}
} catch (error) {
this._helper.deleteCache(cachedEntry.formattedKey);
return def;
}
}
/**
* 将数据存入缓存。
*
* @param {string} key - 缓存的键。
* @param {string} formattedKey - 格式化的键。
* @param {string} rawData - 原始的 JSON 字符串。
*/
_setCache(key, formattedKey, rawData) {
this._helper.setCache(key, { formattedKey, rawData });
}
/**
* 获取缓存条目。
*
* @param {string} key - 缓存的键。
* @returns {{ formattedKey: string, rawData: string | Promise<any> } | undefined} - 缓存条目。
*/
_getCacheEntry(key) {
return this._helper.getCache(key);
}
/**
* 处理缓存条目,返回解析后的数据或默认值。
*
* @param {{ formattedKey: string, rawData: string | Promise<any> }} cachedEntry - 缓存条目。
* @param {*} [def=null] - 默认返回的值。
* @returns {Promise<T | null>} - 解析后的数据或默认值。
*/
async _handleCachedEntry(cachedEntry, def = null) {
if (typeof cachedEntry.rawData === "string") {
try {
const data = JSON.parse(cachedEntry.rawData);
if (this._helper.isNotExpired(data)) {
const parsedData = data.value;
this._setCache(cachedEntry.formattedKey, cachedEntry.formattedKey, Promise.resolve(parsedData));
return parsedData;
} else {
this.remove(cachedEntry.formattedKey);
return def;
}
} catch (error) {
this._helper.deleteCache(cachedEntry.formattedKey);
return def;
}
} else if (cachedEntry.rawData instanceof Promise) {
try {
return await cachedEntry.rawData;
} catch (error) {
this._helper.deleteCache(cachedEntry.formattedKey);
return def;
}
}
return def;
}
/**
* 解析并缓存数据。
*
* @param {string} storedKey - 存储的键。
* @param {string} val - 存储的值。
* @returns {Promise<any>} - 解析后的数据。
*/
async _parseAndCacheData(storedKey, val) {
const decVal = this._helper.decryptData(val);
const data = JSON.parse(decVal);
if (this._helper.isNotExpired(data)) {
this._setCache(storedKey.toLowerCase(), storedKey, decVal);
return data;
} else {
this.remove(storedKey.toLowerCase());
throw new Error("\u6570\u636E\u5DF2\u8FC7\u671F");
}
}
/**
* 清除带有前缀的缓存项。
*
* @private
*/
_clearWithPrefix() {
for (let i = 0; i < this._adapter.storage.length; i++) {
const key = this._adapter.storage.key(i);
if (key && key.startsWith(`${this._prefixKey}_`.toUpperCase())) {
this._adapter.storage.removeItem(key);
}
}
}
}
const createStorage$1 = ({
prefixKey = "WEBSTORAGE",
storage = sessionStorage,
key = cacheCipher.key,
iv = cacheCipher.iv,
timeout = null,
hasEncrypt = true,
storageType = "WEBSTORAGE"
} = {}) => {
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 });
if (storageType === StorageType.WEBSTORAGE) {
return new WebStorage({ storage, prefixKey, hasEncrypt, encryption });
}
};
const createStorageIndexedDB = ({
prefixKey = "INDEXEDDB_APP",
storage = sessionStorage,
key = cacheCipher.key,
iv = cacheCipher.iv,
timeout = null,
hasEncrypt = true,
storageType = "INDEXEDDB",
cacheName = "INDEXEDDB_APP"
} = {}) => {
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 });
if (storageType == StorageType.INDEXEDDB) {
return new IndexedDBStorage({ prefixKey, hasEncrypt, encryption, cacheName });
}
};
const { DEFAULT_CACHE_TIME, enableStorageEncryption, prefixKey } = cacheConfig;
const createOptions = (storage, options = {}) => {
return {
// No encryption in debug mode
hasEncrypt: enableStorageEncryption,
storage,
prefixKey,
...options
};
};
const createStorage = (storage = sessionStorage, options = {}) => {
return createStorage$1(createOptions(storage, options));
};
const createSessionStorage = (options = {}) => {
return createStorage(sessionStorage, { ...options, timeout: DEFAULT_CACHE_TIME });
};
const createLocalStorage = (options = {}) => {
return createStorage(localStorage, { ...options, timeout: DEFAULT_CACHE_TIME });
};
const createIndexedDBStorage = (options = {}) => {
return createStorageIndexedDB({ ...options, timeout: DEFAULT_CACHE_TIME, storageType: "INDEXEDDB" });
};
exports.createIndexedDBStorage = createIndexedDBStorage;
exports.createLocalStorage = createLocalStorage;
exports.createSessionStorage = createSessionStorage;