angular-localforage
Version:
Angular service & directive for https://github.com/mozilla/localForage (Offline storage, improved.)
271 lines (224 loc) • 8.07 kB
JavaScript
// If IndexedDB isn't available, we'll fall back to localStorage.
// Note that this will have considerable performance and storage
// side-effects (all data will be serialized on save and only data that
// can be converted to a string via `JSON.stringify()` will be saved).
import serializer from '../utils/serializer';
import Promise from '../utils/promise';
import executeCallback from '../utils/executeCallback';
// Config the localStorage backend, using options set in the config.
function _initStorage(options) {
var self = this;
var dbInfo = {};
if (options) {
for (var i in options) {
dbInfo[i] = options[i];
}
}
dbInfo.keyPrefix = dbInfo.name + '/';
if (dbInfo.storeName !== self._defaultConfig.storeName) {
dbInfo.keyPrefix += dbInfo.storeName + '/';
}
self._dbInfo = dbInfo;
dbInfo.serializer = serializer;
return Promise.resolve();
}
// Remove all keys from the datastore, effectively destroying all data in
// the app's key/value store!
function clear(callback) {
var self = this;
var promise = self.ready().then(function() {
var keyPrefix = self._dbInfo.keyPrefix;
for (var i = localStorage.length - 1; i >= 0; i--) {
var key = localStorage.key(i);
if (key.indexOf(keyPrefix) === 0) {
localStorage.removeItem(key);
}
}
});
executeCallback(promise, callback);
return promise;
}
// Retrieve an item from the store. Unlike the original async_storage
// library in Gaia, we don't modify return values at all. If a key's value
// is `undefined`, we pass that value to the callback function.
function getItem(key, callback) {
var self = this;
// Cast the key to a string, as that's all we can set as a key.
if (typeof key !== 'string') {
console.warn(key +
' used as a key, but it is not a string.');
key = String(key);
}
var promise = self.ready().then(function() {
var dbInfo = self._dbInfo;
var result = localStorage.getItem(dbInfo.keyPrefix + key);
// If a result was found, parse it from the serialized
// string into a JS object. If result isn't truthy, the key
// is likely undefined and we'll pass it straight to the
// callback.
if (result) {
result = dbInfo.serializer.deserialize(result);
}
return result;
});
executeCallback(promise, callback);
return promise;
}
// Iterate over all items in the store.
function iterate(iterator, callback) {
var self = this;
var promise = self.ready().then(function() {
var dbInfo = self._dbInfo;
var keyPrefix = dbInfo.keyPrefix;
var keyPrefixLength = keyPrefix.length;
var length = localStorage.length;
// We use a dedicated iterator instead of the `i` variable below
// so other keys we fetch in localStorage aren't counted in
// the `iterationNumber` argument passed to the `iterate()`
// callback.
//
// See: github.com/mozilla/localForage/pull/435#discussion_r38061530
var iterationNumber = 1;
for (var i = 0; i < length; i++) {
var key = localStorage.key(i);
if (key.indexOf(keyPrefix) !== 0) {
continue;
}
var value = localStorage.getItem(key);
// If a result was found, parse it from the serialized
// string into a JS object. If result isn't truthy, the
// key is likely undefined and we'll pass it straight
// to the iterator.
if (value) {
value = dbInfo.serializer.deserialize(value);
}
value = iterator(value, key.substring(keyPrefixLength),
iterationNumber++);
if (value !== void(0)) {
return value;
}
}
});
executeCallback(promise, callback);
return promise;
}
// Same as localStorage's key() method, except takes a callback.
function key(n, callback) {
var self = this;
var promise = self.ready().then(function() {
var dbInfo = self._dbInfo;
var result;
try {
result = localStorage.key(n);
} catch (error) {
result = null;
}
// Remove the prefix from the key, if a key is found.
if (result) {
result = result.substring(dbInfo.keyPrefix.length);
}
return result;
});
executeCallback(promise, callback);
return promise;
}
function keys(callback) {
var self = this;
var promise = self.ready().then(function() {
var dbInfo = self._dbInfo;
var length = localStorage.length;
var keys = [];
for (var i = 0; i < length; i++) {
if (localStorage.key(i).indexOf(dbInfo.keyPrefix) === 0) {
keys.push(localStorage.key(i).substring(dbInfo.keyPrefix.length));
}
}
return keys;
});
executeCallback(promise, callback);
return promise;
}
// Supply the number of keys in the datastore to the callback function.
function length(callback) {
var self = this;
var promise = self.keys().then(function(keys) {
return keys.length;
});
executeCallback(promise, callback);
return promise;
}
// Remove an item from the store, nice and simple.
function removeItem(key, callback) {
var self = this;
// Cast the key to a string, as that's all we can set as a key.
if (typeof key !== 'string') {
console.warn(key +
' used as a key, but it is not a string.');
key = String(key);
}
var promise = self.ready().then(function() {
var dbInfo = self._dbInfo;
localStorage.removeItem(dbInfo.keyPrefix + key);
});
executeCallback(promise, callback);
return promise;
}
// Set a key's value and run an optional callback once the value is set.
// Unlike Gaia's implementation, the callback function is passed the value,
// in case you want to operate on that value only after you're sure it
// saved, or something like that.
function setItem(key, value, callback) {
var self = this;
// Cast the key to a string, as that's all we can set as a key.
if (typeof key !== 'string') {
console.warn(key +
' used as a key, but it is not a string.');
key = String(key);
}
var promise = self.ready().then(function() {
// Convert undefined values to null.
// https://github.com/mozilla/localForage/pull/42
if (value === undefined) {
value = null;
}
// Save the original value to pass to the callback.
var originalValue = value;
return new Promise(function(resolve, reject) {
var dbInfo = self._dbInfo;
dbInfo.serializer.serialize(value, function(value, error) {
if (error) {
reject(error);
} else {
try {
localStorage.setItem(dbInfo.keyPrefix + key, value);
resolve(originalValue);
} catch (e) {
// localStorage capacity exceeded.
// TODO: Make this a specific error/event.
if (e.name === 'QuotaExceededError' ||
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
reject(e);
}
reject(e);
}
}
});
});
});
executeCallback(promise, callback);
return promise;
}
var localStorageWrapper = {
_driver: 'localStorageWrapper',
_initStorage: _initStorage,
// Default API, from Gaia/localStorage.
iterate: iterate,
getItem: getItem,
setItem: setItem,
removeItem: removeItem,
clear: clear,
length: length,
key: key,
keys: keys
};
export default localStorageWrapper;