Expiring Local Storage Objects in JavaScript

All the modern browsers have multiple types of storage mechanisms for using in your web applications. You may have already heard of cookies which are small bits of information you can store and they will be automatically expired. However, cookies can only store small amounts of information. The other kind of storage is sessionStorage, where you can store big chunks of information. However all the data stored in this will be lost as soon as you close the browser tab.

Local Storage

Local storage provides an intermediary option, it can store large amount of information and it will not be lost after the user closes the tab or browser. The data is persisted across sessions.

Browser Storages
Browser Storages

Browser Support

This is supported in almost all browsers. As per the website https://caniuse.com, this is supported in more than 98% of the browsers. Here is the browser support chart for this feature.

Solution

When compared with cookies and sessionStorage, it may seem like best of both worlds, however, there is a problem. The statement large amount should be seen from the perspective of web technologies. For example you can store only upto 5MB of text data per domain using localStorage in Chrome Browser for each website. So, if this website wants to store some information in localStorage it can only store upto 5MB, once this website tries to store any more info, the browser will spit a javascript error and may crash the website. However, other website will still function normally and can save data themselves as well. The limit will be different for each browser. So, we want to store large chunks of data across sessions, but we still want to have an option of invalidating after a certain period of time. Let us see how to go on about solving the problem.

The solution is to wrap localStorage API with a utility. Your application’s logic for accessing should encapsulated in a single class. Using localStorage revolves around storing item with a key and fetching the item previously stored.

The utility we are going to create can provide utility methods to save and load from localStorage. The save method will store the item along with the timestamp, where are load method should first check if the item has already expired before returning it. Here is the that does just that.

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const EXPIRY_IN_HOURS = 6;

/**
* Fetches the previously stored item from the local storage, checks if the item
* has expired, if so, it will return null, otherwise it will return the item.
*
* @param {string} key The key to lookup the item.
*/
export const load = (key) => {
const jsonValue = localStorage[key];
const item = jsonValue ? JSON.parse(jsonValue) : null;

if (hasExpired(item)) {
delete localStorage[key];
return null;
}
return value.data;
};

/**
* Saves an item to the local storage along with the current time.
*
* @param {string} key The key to store the item.
* @param {*} data The data to store.
*/
export const save = (key, data) => {
if (key && data) {
localStorage[key] = JSON.stringify({ data, timestamp: new Date() });
} else {
console.error(`Invalid key or data: Key: ${key}, data: ${data}`);
}
};

/**
* Check if the item's age and determines if it has to be invalidated.
*/
const hasExpired = (item) => {
const dt1 = new Date(item.timestamp);
const dt2 = new Date();
diffHours = Math.floor(dt2 - dt1) / (1000 * 60 * 60));

return !item || EXPIRY_IN_HOURS < diffHours;
};

In this snippet, we have one auxiliary method to help with detection of expiration. The method hasExpired will calculate the difference in hours. It uses the current timestamp and the and timestamp stored along with data to calculate the age. If the age of the item is more than the expire hours we set, we return true from this method. The load method invokes the hasExpired method to check if the item is expired and return null after deleting the item from localStorage.

Keep note of the issues that can arise out of this, if the user happens to travel between timezones the expiry may not work appropriately, the solution is to use UTC timestamps all the time.

Also, we only remove the items when user tries to access them so, if we store an item and never read it again, we may leave that item forever in localStorage. We can solve that as well but it is for a different post.

There can be other frameworks that may solve this problem elegantly, but what is more interesting than solving problems ourselves.