UNPKG

@aws-amplify/cache

Version:

Cache category of aws-amplify

434 lines • 16.2 kB
"use strict"; // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var core_1 = require("@aws-amplify/core"); var Utils_1 = require("./Utils"); var StorageCache_1 = require("./StorageCache"); var logger = new core_1.ConsoleLogger('Cache'); /** * Customized storage based on the SessionStorage or LocalStorage with LRU implemented */ var BrowserStorageCacheClass = /** @class */ (function (_super) { tslib_1.__extends(BrowserStorageCacheClass, _super); /** * initialize the cache * @param config - the configuration of the cache */ function BrowserStorageCacheClass(config) { var _this = this; var cacheConfig = config ? Object.assign({}, Utils_1.defaultConfig, config) : Utils_1.defaultConfig; _this = _super.call(this, cacheConfig) || this; _this.config.storage = cacheConfig.storage; _this.getItem = _this.getItem.bind(_this); _this.setItem = _this.setItem.bind(_this); _this.removeItem = _this.removeItem.bind(_this); return _this; } /** * decrease current size of the cache * * @private * @param amount - the amount of the cache size which needs to be decreased */ BrowserStorageCacheClass.prototype._decreaseCurSizeInBytes = function (amount) { var curSize = this.getCacheCurSize(); this.config.storage.setItem(this.cacheCurSizeKey, (curSize - amount).toString()); }; /** * increase current size of the cache * * @private * @param amount - the amount of the cache szie which need to be increased */ BrowserStorageCacheClass.prototype._increaseCurSizeInBytes = function (amount) { var curSize = this.getCacheCurSize(); this.config.storage.setItem(this.cacheCurSizeKey, (curSize + amount).toString()); }; /** * update the visited time if item has been visited * * @private * @param item - the item which need to be refreshed * @param prefixedKey - the key of the item * * @return the refreshed item */ BrowserStorageCacheClass.prototype._refreshItem = function (item, prefixedKey) { item.visitedTime = Utils_1.getCurrTime(); this.config.storage.setItem(prefixedKey, JSON.stringify(item)); return item; }; /** * check wether item is expired * * @private * @param key - the key of the item * * @return true if the item is expired. */ BrowserStorageCacheClass.prototype._isExpired = function (key) { var text = this.config.storage.getItem(key); var item = JSON.parse(text); if (Utils_1.getCurrTime() >= item.expires) { return true; } return false; }; /** * delete item from cache * * @private * @param prefixedKey - the key of the item * @param size - optional, the byte size of the item */ BrowserStorageCacheClass.prototype._removeItem = function (prefixedKey, size) { var itemSize = size ? size : JSON.parse(this.config.storage.getItem(prefixedKey)).byteSize; this._decreaseCurSizeInBytes(itemSize); // remove the cache item this.config.storage.removeItem(prefixedKey); }; /** * put item into cache * * @private * @param prefixedKey - the key of the item * @param itemData - the value of the item * @param itemSizeInBytes - the byte size of the item */ BrowserStorageCacheClass.prototype._setItem = function (prefixedKey, item) { // update the cache size this._increaseCurSizeInBytes(item.byteSize); try { this.config.storage.setItem(prefixedKey, JSON.stringify(item)); } catch (setItemErr) { // if failed, we need to rollback the cache size this._decreaseCurSizeInBytes(item.byteSize); logger.error("Failed to set item " + setItemErr); } }; /** * total space needed when poping out items * * @private * @param itemSize * * @return total space needed */ BrowserStorageCacheClass.prototype._sizeToPop = function (itemSize) { var spaceItemNeed = this.getCacheCurSize() + itemSize - this.config.capacityInBytes; var cacheThresholdSpace = (1 - this.config.warningThreshold) * this.config.capacityInBytes; return spaceItemNeed > cacheThresholdSpace ? spaceItemNeed : cacheThresholdSpace; }; /** * see whether cache is full * * @private * @param itemSize * * @return true if cache is full */ BrowserStorageCacheClass.prototype._isCacheFull = function (itemSize) { return itemSize + this.getCacheCurSize() > this.config.capacityInBytes; }; /** * scan the storage and find out all the keys owned by this cache * also clean the expired keys while scanning * * @private * * @return array of keys */ BrowserStorageCacheClass.prototype._findValidKeys = function () { var keys = []; var keyInCache = []; // get all keys in Storage for (var i = 0; i < this.config.storage.length; i += 1) { keyInCache.push(this.config.storage.key(i)); } // find those items which belong to our cache and also clean those expired items for (var i = 0; i < keyInCache.length; i += 1) { var key = keyInCache[i]; if (key.indexOf(this.config.keyPrefix) === 0 && key !== this.cacheCurSizeKey) { if (this._isExpired(key)) { this._removeItem(key); } else { keys.push(key); } } } return keys; }; /** * get all the items we have, sort them by their priority, * if priority is same, sort them by their last visited time * pop out items from the low priority (5 is the lowest) * * @private * @param keys - all the keys in this cache * @param sizeToPop - the total size of the items which needed to be poped out */ BrowserStorageCacheClass.prototype._popOutItems = function (keys, sizeToPop) { var items = []; var remainedSize = sizeToPop; // get the items from Storage for (var i = 0; i < keys.length; i += 1) { var val = this.config.storage.getItem(keys[i]); if (val != null) { var item = JSON.parse(val); items.push(item); } } // first compare priority // then compare visited time items.sort(function (a, b) { if (a.priority > b.priority) { return -1; } else if (a.priority < b.priority) { return 1; } else { if (a.visitedTime < b.visitedTime) { return -1; } else return 1; } }); for (var i = 0; i < items.length; i += 1) { // pop out items until we have enough room for new item this._removeItem(items[i].key, items[i].byteSize); remainedSize -= items[i].byteSize; if (remainedSize <= 0) { return; } } }; /** * Set item into cache. You can put number, string, boolean or object. * The cache will first check whether has the same key. * If it has, it will delete the old item and then put the new item in * The cache will pop out items if it is full * You can specify the cache item options. The cache will abort and output a warning: * If the key is invalid * If the size of the item exceeds itemMaxSize. * If the value is undefined * If incorrect cache item configuration * If error happened with browser storage * * @param key - the key of the item * @param value - the value of the item * @param {Object} [options] - optional, the specified meta-data */ BrowserStorageCacheClass.prototype.setItem = function (key, value, options) { logger.log("Set item: key is " + key + ", value is " + value + " with options: " + options); var prefixedKey = this.config.keyPrefix + key; // invalid keys if (prefixedKey === this.config.keyPrefix || prefixedKey === this.cacheCurSizeKey) { logger.warn("Invalid key: should not be empty or 'CurSize'"); return; } if (typeof value === 'undefined') { logger.warn("The value of item should not be undefined!"); return; } var cacheItemOptions = { priority: options && options.priority !== undefined ? options.priority : this.config.defaultPriority, expires: options && options.expires !== undefined ? options.expires : this.config.defaultTTL + Utils_1.getCurrTime(), }; if (cacheItemOptions.priority < 1 || cacheItemOptions.priority > 5) { logger.warn("Invalid parameter: priority due to out or range. It should be within 1 and 5."); return; } var item = this.fillCacheItem(prefixedKey, value, cacheItemOptions); // check wether this item is too big; if (item.byteSize > this.config.itemMaxSize) { logger.warn("Item with key: " + key + " you are trying to put into is too big!"); return; } try { // first look into the storage, if it exists, delete it. var val = this.config.storage.getItem(prefixedKey); if (val) { this._removeItem(prefixedKey, JSON.parse(val).byteSize); } // check whether the cache is full if (this._isCacheFull(item.byteSize)) { var validKeys = this._findValidKeys(); // check again and then pop out items if (this._isCacheFull(item.byteSize)) { var sizeToPop = this._sizeToPop(item.byteSize); this._popOutItems(validKeys, sizeToPop); } } // put item in the cache // may failed due to storage full this._setItem(prefixedKey, item); } catch (e) { logger.warn("setItem failed! " + e); } }; /** * Get item from cache. It will return null if item doesn’t exist or it has been expired. * If you specified callback function in the options, * then the function will be executed if no such item in the cache * and finally put the return value into cache. * Please make sure the callback function will return the value you want to put into the cache. * The cache will abort output a warning: * If the key is invalid * If error happened with browser storage * * @param key - the key of the item * @param {Object} [options] - the options of callback function * * @return - return the value of the item */ BrowserStorageCacheClass.prototype.getItem = function (key, options) { logger.log("Get item: key is " + key + " with options " + options); var ret = null; var prefixedKey = this.config.keyPrefix + key; if (prefixedKey === this.config.keyPrefix || prefixedKey === this.cacheCurSizeKey) { logger.warn("Invalid key: should not be empty or 'CurSize'"); return null; } try { ret = this.config.storage.getItem(prefixedKey); if (ret != null) { if (this._isExpired(prefixedKey)) { // if expired, remove that item and return null this._removeItem(prefixedKey, JSON.parse(ret).byteSize); ret = null; } else { // if not expired, great, return the value and refresh it var item = JSON.parse(ret); item = this._refreshItem(item, prefixedKey); return item.data; } } if (options && options.callback !== undefined) { var val = options.callback(); if (val !== null) { this.setItem(key, val, options); } return val; } return null; } catch (e) { logger.warn("getItem failed! " + e); return null; } }; /** * remove item from the cache * The cache will abort output a warning: * If error happened with browser storage * @param key - the key of the item */ BrowserStorageCacheClass.prototype.removeItem = function (key) { logger.log("Remove item: key is " + key); var prefixedKey = this.config.keyPrefix + key; if (prefixedKey === this.config.keyPrefix || prefixedKey === this.cacheCurSizeKey) { return; } try { var val = this.config.storage.getItem(prefixedKey); if (val) { this._removeItem(prefixedKey, JSON.parse(val).byteSize); } } catch (e) { logger.warn("removeItem failed! " + e); } }; /** * clear the entire cache * The cache will abort output a warning: * If error happened with browser storage */ BrowserStorageCacheClass.prototype.clear = function () { logger.log("Clear Cache"); var keysToRemove = []; for (var i = 0; i < this.config.storage.length; i += 1) { var key = this.config.storage.key(i); if (key.indexOf(this.config.keyPrefix) === 0) { keysToRemove.push(key); } } try { for (var i = 0; i < keysToRemove.length; i += 1) { this.config.storage.removeItem(keysToRemove[i]); } } catch (e) { logger.warn("clear failed! " + e); } }; /** * Return all the keys in the cache. * * @return - all keys in the cache */ BrowserStorageCacheClass.prototype.getAllKeys = function () { var keys = []; for (var i = 0; i < this.config.storage.length; i += 1) { var key = this.config.storage.key(i); if (key.indexOf(this.config.keyPrefix) === 0 && key !== this.cacheCurSizeKey) { keys.push(key.substring(this.config.keyPrefix.length)); } } return keys; }; /** * return the current size of the cache * * @return - current size of the cache */ BrowserStorageCacheClass.prototype.getCacheCurSize = function () { var ret = this.config.storage.getItem(this.cacheCurSizeKey); if (!ret) { this.config.storage.setItem(this.cacheCurSizeKey, '0'); ret = '0'; } return Number(ret); }; /** * Return a new instance of cache with customized configuration. * @param config - the customized configuration * * @return - new instance of Cache */ BrowserStorageCacheClass.prototype.createInstance = function (config) { if (!config.keyPrefix || config.keyPrefix === Utils_1.defaultConfig.keyPrefix) { logger.error('invalid keyPrefix, setting keyPrefix with timeStamp'); config.keyPrefix = Utils_1.getCurrTime.toString(); } return new BrowserStorageCacheClass(config); }; return BrowserStorageCacheClass; }(StorageCache_1.StorageCache)); exports.BrowserStorageCacheClass = BrowserStorageCacheClass; exports.BrowserStorageCache = new BrowserStorageCacheClass(); core_1.Amplify.register(exports.BrowserStorageCache); //# sourceMappingURL=BrowserStorageCache.js.map