UNPKG

aura-storage

Version:
764 lines (752 loc) 24.3 kB
'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;