UNPKG

16 kBJavaScriptView Raw
1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3import { __extends } from "tslib";
4import { Amplify, ConsoleLogger as Logger } from '@aws-amplify/core';
5import { defaultConfig, getCurrTime } from './Utils';
6import { StorageCache } from './StorageCache';
7var logger = new Logger('Cache');
8/**
9 * Customized storage based on the SessionStorage or LocalStorage with LRU implemented
10 */
11var BrowserStorageCacheClass = /** @class */ (function (_super) {
12 __extends(BrowserStorageCacheClass, _super);
13 /**
14 * initialize the cache
15 * @param config - the configuration of the cache
16 */
17 function BrowserStorageCacheClass(config) {
18 var _this = this;
19 var cacheConfig = config
20 ? Object.assign({}, defaultConfig, config)
21 : defaultConfig;
22 _this = _super.call(this, cacheConfig) || this;
23 _this.config.storage = cacheConfig.storage;
24 _this.getItem = _this.getItem.bind(_this);
25 _this.setItem = _this.setItem.bind(_this);
26 _this.removeItem = _this.removeItem.bind(_this);
27 return _this;
28 }
29 /**
30 * decrease current size of the cache
31 *
32 * @private
33 * @param amount - the amount of the cache size which needs to be decreased
34 */
35 BrowserStorageCacheClass.prototype._decreaseCurSizeInBytes = function (amount) {
36 var curSize = this.getCacheCurSize();
37 this.config.storage.setItem(this.cacheCurSizeKey, (curSize - amount).toString());
38 };
39 /**
40 * increase current size of the cache
41 *
42 * @private
43 * @param amount - the amount of the cache szie which need to be increased
44 */
45 BrowserStorageCacheClass.prototype._increaseCurSizeInBytes = function (amount) {
46 var curSize = this.getCacheCurSize();
47 this.config.storage.setItem(this.cacheCurSizeKey, (curSize + amount).toString());
48 };
49 /**
50 * update the visited time if item has been visited
51 *
52 * @private
53 * @param item - the item which need to be refreshed
54 * @param prefixedKey - the key of the item
55 *
56 * @return the refreshed item
57 */
58 BrowserStorageCacheClass.prototype._refreshItem = function (item, prefixedKey) {
59 item.visitedTime = getCurrTime();
60 this.config.storage.setItem(prefixedKey, JSON.stringify(item));
61 return item;
62 };
63 /**
64 * check wether item is expired
65 *
66 * @private
67 * @param key - the key of the item
68 *
69 * @return true if the item is expired.
70 */
71 BrowserStorageCacheClass.prototype._isExpired = function (key) {
72 var text = this.config.storage.getItem(key);
73 var item = JSON.parse(text);
74 if (getCurrTime() >= item.expires) {
75 return true;
76 }
77 return false;
78 };
79 /**
80 * delete item from cache
81 *
82 * @private
83 * @param prefixedKey - the key of the item
84 * @param size - optional, the byte size of the item
85 */
86 BrowserStorageCacheClass.prototype._removeItem = function (prefixedKey, size) {
87 var itemSize = size
88 ? size
89 : JSON.parse(this.config.storage.getItem(prefixedKey)).byteSize;
90 this._decreaseCurSizeInBytes(itemSize);
91 // remove the cache item
92 this.config.storage.removeItem(prefixedKey);
93 };
94 /**
95 * put item into cache
96 *
97 * @private
98 * @param prefixedKey - the key of the item
99 * @param itemData - the value of the item
100 * @param itemSizeInBytes - the byte size of the item
101 */
102 BrowserStorageCacheClass.prototype._setItem = function (prefixedKey, item) {
103 // update the cache size
104 this._increaseCurSizeInBytes(item.byteSize);
105 try {
106 this.config.storage.setItem(prefixedKey, JSON.stringify(item));
107 }
108 catch (setItemErr) {
109 // if failed, we need to rollback the cache size
110 this._decreaseCurSizeInBytes(item.byteSize);
111 logger.error("Failed to set item " + setItemErr);
112 }
113 };
114 /**
115 * total space needed when poping out items
116 *
117 * @private
118 * @param itemSize
119 *
120 * @return total space needed
121 */
122 BrowserStorageCacheClass.prototype._sizeToPop = function (itemSize) {
123 var spaceItemNeed = this.getCacheCurSize() + itemSize - this.config.capacityInBytes;
124 var cacheThresholdSpace = (1 - this.config.warningThreshold) * this.config.capacityInBytes;
125 return spaceItemNeed > cacheThresholdSpace
126 ? spaceItemNeed
127 : cacheThresholdSpace;
128 };
129 /**
130 * see whether cache is full
131 *
132 * @private
133 * @param itemSize
134 *
135 * @return true if cache is full
136 */
137 BrowserStorageCacheClass.prototype._isCacheFull = function (itemSize) {
138 return itemSize + this.getCacheCurSize() > this.config.capacityInBytes;
139 };
140 /**
141 * scan the storage and find out all the keys owned by this cache
142 * also clean the expired keys while scanning
143 *
144 * @private
145 *
146 * @return array of keys
147 */
148 BrowserStorageCacheClass.prototype._findValidKeys = function () {
149 var keys = [];
150 var keyInCache = [];
151 // get all keys in Storage
152 for (var i = 0; i < this.config.storage.length; i += 1) {
153 keyInCache.push(this.config.storage.key(i));
154 }
155 // find those items which belong to our cache and also clean those expired items
156 for (var i = 0; i < keyInCache.length; i += 1) {
157 var key = keyInCache[i];
158 if (key.indexOf(this.config.keyPrefix) === 0 &&
159 key !== this.cacheCurSizeKey) {
160 if (this._isExpired(key)) {
161 this._removeItem(key);
162 }
163 else {
164 keys.push(key);
165 }
166 }
167 }
168 return keys;
169 };
170 /**
171 * get all the items we have, sort them by their priority,
172 * if priority is same, sort them by their last visited time
173 * pop out items from the low priority (5 is the lowest)
174 *
175 * @private
176 * @param keys - all the keys in this cache
177 * @param sizeToPop - the total size of the items which needed to be poped out
178 */
179 BrowserStorageCacheClass.prototype._popOutItems = function (keys, sizeToPop) {
180 var items = [];
181 var remainedSize = sizeToPop;
182 // get the items from Storage
183 for (var i = 0; i < keys.length; i += 1) {
184 var val = this.config.storage.getItem(keys[i]);
185 if (val != null) {
186 var item = JSON.parse(val);
187 items.push(item);
188 }
189 }
190 // first compare priority
191 // then compare visited time
192 items.sort(function (a, b) {
193 if (a.priority > b.priority) {
194 return -1;
195 }
196 else if (a.priority < b.priority) {
197 return 1;
198 }
199 else {
200 if (a.visitedTime < b.visitedTime) {
201 return -1;
202 }
203 else
204 return 1;
205 }
206 });
207 for (var i = 0; i < items.length; i += 1) {
208 // pop out items until we have enough room for new item
209 this._removeItem(items[i].key, items[i].byteSize);
210 remainedSize -= items[i].byteSize;
211 if (remainedSize <= 0) {
212 return;
213 }
214 }
215 };
216 /**
217 * Set item into cache. You can put number, string, boolean or object.
218 * The cache will first check whether has the same key.
219 * If it has, it will delete the old item and then put the new item in
220 * The cache will pop out items if it is full
221 * You can specify the cache item options. The cache will abort and output a warning:
222 * If the key is invalid
223 * If the size of the item exceeds itemMaxSize.
224 * If the value is undefined
225 * If incorrect cache item configuration
226 * If error happened with browser storage
227 *
228 * @param key - the key of the item
229 * @param value - the value of the item
230 * @param {Object} [options] - optional, the specified meta-data
231 */
232 BrowserStorageCacheClass.prototype.setItem = function (key, value, options) {
233 logger.log("Set item: key is " + key + ", value is " + value + " with options: " + options);
234 var prefixedKey = this.config.keyPrefix + key;
235 // invalid keys
236 if (prefixedKey === this.config.keyPrefix ||
237 prefixedKey === this.cacheCurSizeKey) {
238 logger.warn("Invalid key: should not be empty or 'CurSize'");
239 return;
240 }
241 if (typeof value === 'undefined') {
242 logger.warn("The value of item should not be undefined!");
243 return;
244 }
245 var cacheItemOptions = {
246 priority: options && options.priority !== undefined
247 ? options.priority
248 : this.config.defaultPriority,
249 expires: options && options.expires !== undefined
250 ? options.expires
251 : this.config.defaultTTL + getCurrTime(),
252 };
253 if (cacheItemOptions.priority < 1 || cacheItemOptions.priority > 5) {
254 logger.warn("Invalid parameter: priority due to out or range. It should be within 1 and 5.");
255 return;
256 }
257 var item = this.fillCacheItem(prefixedKey, value, cacheItemOptions);
258 // check wether this item is too big;
259 if (item.byteSize > this.config.itemMaxSize) {
260 logger.warn("Item with key: " + key + " you are trying to put into is too big!");
261 return;
262 }
263 try {
264 // first look into the storage, if it exists, delete it.
265 var val = this.config.storage.getItem(prefixedKey);
266 if (val) {
267 this._removeItem(prefixedKey, JSON.parse(val).byteSize);
268 }
269 // check whether the cache is full
270 if (this._isCacheFull(item.byteSize)) {
271 var validKeys = this._findValidKeys();
272 // check again and then pop out items
273 if (this._isCacheFull(item.byteSize)) {
274 var sizeToPop = this._sizeToPop(item.byteSize);
275 this._popOutItems(validKeys, sizeToPop);
276 }
277 }
278 // put item in the cache
279 // may failed due to storage full
280 this._setItem(prefixedKey, item);
281 }
282 catch (e) {
283 logger.warn("setItem failed! " + e);
284 }
285 };
286 /**
287 * Get item from cache. It will return null if item doesn’t exist or it has been expired.
288 * If you specified callback function in the options,
289 * then the function will be executed if no such item in the cache
290 * and finally put the return value into cache.
291 * Please make sure the callback function will return the value you want to put into the cache.
292 * The cache will abort output a warning:
293 * If the key is invalid
294 * If error happened with browser storage
295 *
296 * @param key - the key of the item
297 * @param {Object} [options] - the options of callback function
298 *
299 * @return - return the value of the item
300 */
301 BrowserStorageCacheClass.prototype.getItem = function (key, options) {
302 logger.log("Get item: key is " + key + " with options " + options);
303 var ret = null;
304 var prefixedKey = this.config.keyPrefix + key;
305 if (prefixedKey === this.config.keyPrefix ||
306 prefixedKey === this.cacheCurSizeKey) {
307 logger.warn("Invalid key: should not be empty or 'CurSize'");
308 return null;
309 }
310 try {
311 ret = this.config.storage.getItem(prefixedKey);
312 if (ret != null) {
313 if (this._isExpired(prefixedKey)) {
314 // if expired, remove that item and return null
315 this._removeItem(prefixedKey, JSON.parse(ret).byteSize);
316 ret = null;
317 }
318 else {
319 // if not expired, great, return the value and refresh it
320 var item = JSON.parse(ret);
321 item = this._refreshItem(item, prefixedKey);
322 return item.data;
323 }
324 }
325 if (options && options.callback !== undefined) {
326 var val = options.callback();
327 if (val !== null) {
328 this.setItem(key, val, options);
329 }
330 return val;
331 }
332 return null;
333 }
334 catch (e) {
335 logger.warn("getItem failed! " + e);
336 return null;
337 }
338 };
339 /**
340 * remove item from the cache
341 * The cache will abort output a warning:
342 * If error happened with browser storage
343 * @param key - the key of the item
344 */
345 BrowserStorageCacheClass.prototype.removeItem = function (key) {
346 logger.log("Remove item: key is " + key);
347 var prefixedKey = this.config.keyPrefix + key;
348 if (prefixedKey === this.config.keyPrefix ||
349 prefixedKey === this.cacheCurSizeKey) {
350 return;
351 }
352 try {
353 var val = this.config.storage.getItem(prefixedKey);
354 if (val) {
355 this._removeItem(prefixedKey, JSON.parse(val).byteSize);
356 }
357 }
358 catch (e) {
359 logger.warn("removeItem failed! " + e);
360 }
361 };
362 /**
363 * clear the entire cache
364 * The cache will abort output a warning:
365 * If error happened with browser storage
366 */
367 BrowserStorageCacheClass.prototype.clear = function () {
368 logger.log("Clear Cache");
369 var keysToRemove = [];
370 for (var i = 0; i < this.config.storage.length; i += 1) {
371 var key = this.config.storage.key(i);
372 if (key.indexOf(this.config.keyPrefix) === 0) {
373 keysToRemove.push(key);
374 }
375 }
376 try {
377 for (var i = 0; i < keysToRemove.length; i += 1) {
378 this.config.storage.removeItem(keysToRemove[i]);
379 }
380 }
381 catch (e) {
382 logger.warn("clear failed! " + e);
383 }
384 };
385 /**
386 * Return all the keys in the cache.
387 *
388 * @return - all keys in the cache
389 */
390 BrowserStorageCacheClass.prototype.getAllKeys = function () {
391 var keys = [];
392 for (var i = 0; i < this.config.storage.length; i += 1) {
393 var key = this.config.storage.key(i);
394 if (key.indexOf(this.config.keyPrefix) === 0 &&
395 key !== this.cacheCurSizeKey) {
396 keys.push(key.substring(this.config.keyPrefix.length));
397 }
398 }
399 return keys;
400 };
401 /**
402 * return the current size of the cache
403 *
404 * @return - current size of the cache
405 */
406 BrowserStorageCacheClass.prototype.getCacheCurSize = function () {
407 var ret = this.config.storage.getItem(this.cacheCurSizeKey);
408 if (!ret) {
409 this.config.storage.setItem(this.cacheCurSizeKey, '0');
410 ret = '0';
411 }
412 return Number(ret);
413 };
414 /**
415 * Return a new instance of cache with customized configuration.
416 * @param config - the customized configuration
417 *
418 * @return - new instance of Cache
419 */
420 BrowserStorageCacheClass.prototype.createInstance = function (config) {
421 if (!config.keyPrefix || config.keyPrefix === defaultConfig.keyPrefix) {
422 logger.error('invalid keyPrefix, setting keyPrefix with timeStamp');
423 config.keyPrefix = getCurrTime.toString();
424 }
425 return new BrowserStorageCacheClass(config);
426 };
427 return BrowserStorageCacheClass;
428}(StorageCache));
429export { BrowserStorageCacheClass };
430export var BrowserStorageCache = new BrowserStorageCacheClass();
431Amplify.register(BrowserStorageCache);
432//# sourceMappingURL=BrowserStorageCache.js.map
\No newline at end of file