Skip to content

Instantly share code, notes, and snippets.

@mshwery
Last active May 7, 2018 18:14
Show Gist options
  • Save mshwery/abb46d8cd85a59e65bf8 to your computer and use it in GitHub Desktop.
Save mshwery/abb46d8cd85a59e65bf8 to your computer and use it in GitHub Desktop.
a localStorage wrapper with time-to-live and JSON stringify/parsing
/**
* store.js
* a localStorage wrapper with time-to-live and JSON stringify/parsing
*
* @author Matt Shwery
* @license MIT (http://www.opensource.org/licenses/mit-license)
*/
store = (function () {
// polyfill for localstorage into stale storage so this data store doesnt ever fail
if (!('localStorage' in window)) {
//if (!Modernizr.localstorage) {
window.localStorage = {
_data: {},
setItem: function (id, val) { return this._data[id] = String(val); },
getItem: function (id) { return this._data.hasOwnProperty(id) ? this._data[id] : undefined; },
removeItem: function (id) { return delete this._data[id]; },
clear: function () { return this._data = {}; }
};
}
var PREFIX = 's';
// prefixes the key with the store.js prefix
function prefix(key) {
return PREFIX + ':' + key;
}
// returns the current time in milliseconds
function now() {
return +new Date();
}
// returns the value associated with the given key, if it exists
// returns null if the key doesn't exist
function getItem(key) {
var item = localStorage.getItem(key);
if (item) {
try {
item = JSON.parse(item);
} catch (err) {
console.warn(err);
}
}
return item;
}
// saves a new key/value pair if the key doesn't exist, otherwise
// updates the value
function setItem(key, value) {
var valueString = value;
try {
valueString = JSON.stringify(valueString);
} catch (err) {
console.warn(err);
}
// try to set the item in localStorage
// if the attempt failed then this block executes
if (!trySetItem(key, valueString)) {
// try to remove expired stuff
removeExpiredItems();
// try again and return the item regardless of failure
trySetItem(key, valueString);
}
// either way, return the original value, not the stringified one
return value;
}
function trySetItem(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch (e) {
return false;
}
}
// removes expired items from cache
function removeExpiredItems() {
// get localStorage keys as array
// filter out only ones that include our localStorage PREFIX
var items = Object.keys(localStorage).filter(belongsToCache);
items.forEach(getItemOrExpire);
}
function getItemOrExpire(key) {
var item = getItem(key);
// if there's a expired ttl, remove the item
if (item && item.ttl && ttlIsExpired(item.ttl)) {
return removeItem(key);
}
return item;
}
function ttlIsExpired(ttl) {
return ttl < now();
}
function belongsToCache (key) {
return key.indexOf(PREFIX) !== -1;
}
// accepts optional ttl (in minutes)
function set(key, value, ttl) {
// add prefix to key
key = prefix(key);
// convert ttl from minutes to milleseconds
if (ttl != null) ttl = now() + (ttl * 60000);
// grab any previous items by this key
var oldItem = getItem(key);
// if the old item's ttl isn't expired and there's no ttl passed in use the old ttl
// this allows you to continue to update an object with new values without updating its expiry
// TODO: evaluate need for this
if (!ttl && oldItem && oldItem.ttl && oldItem.ttl >= now()) {
ttl = oldItem.ttl;
}
var item = {
value: value,
ttl: ttl
};
return setItem(key, item);
}
function get(key) {
// add fs and user id prefix to key
key = prefix(key);
var item = getItemOrExpire(key);
// return the retrieved item's value if it exists,
// or if it was removed return the removal's return (undefined)
return (item && item.value) || item;
}
// remove an item from local storage
function removeItem(key) {
return localStorage.removeItem(key);
}
function remove(key) {
return removeItem(prefix(key));
}
// delete all key/value pairs currently stored
function clear() {
// because we only want to delete stuff from localStorage that this function added,
// loop through them one by one and remove them if they have this function's PREFIX
for (var key in localStorage) {
// remove only cached stuff that contains our prefix
if (belongsToCache(key)) removeItem(key);
}
}
return {
get: get,
set: set,
remove: remove,
clear: clear
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment