asyncstorage
Version:
Asynchronous browser storage with multiple back-ends (IndexedDB, WebSQL, localStorage)
1,537 lines (1,298 loc) • 79.8 kB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.storage = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var Promise = require('promise');
var type = require('component-type');
var localForage = require('localforage');
/**
* Setup `localForage`.
*/
localForage.config({
name: 'storage'
});
/**
* Expose `storage()`.
*/
module.exports = storage;
/**
* Facade to get/set/del/count methods.
*
* @param {String|Array|Object} key
* @param {Mixed|Null} val
* @param {Function} cb
*/
function storage(key, val, cb) {
var length = arguments.length;
if (type(arguments[length - 1]) != 'function') length += 1;
switch (length) {
case 3: return val === null
? del(key, cb)
: set(key, val, cb);
case 2: return type(key) == 'object'
? set(key, val)
: get(key, val);
default:
return count(key);
}
}
/**
* Expose methods & properties.
*/
storage.get = get;
storage.set = set;
storage.del = del;
storage.count = count;
storage.clear = clear;
storage.development = false;
storage.forage = localForage;
/**
* Get `key`.
*
* @param {Array|Mixed} key
* @param {Function} cb
*/
function get(key, cb) {
return type(key) != 'array'
? localForage.getItem(key).then(wrap(cb, true), cb)
: Promise.all(key.map(getSubkey)).then(wrap(cb, true), cb);
function getSubkey(key) {
return get(key, function() {}); // noob function to prevent logs
}
}
/**
* Set `val` to `key`.
*
* @param {Array|Mixed} key
* @param {Mixed} val
* @param {Function} cb
*/
function set(key, val, cb) {
return type(key) != 'object'
? localForage.setItem(key, val).then(wrap(cb), cb)
: Promise.all(Object.keys(key).map(setSubkey)).then(wrap(val), val);
function setSubkey(subkey, next) {
return key[subkey] === null
? del(subkey, next)
: set(subkey, key[subkey], next);
}
}
/**
* Delete `key`.
*
* @param {Array|Mixed} key
* @param {Function} cb
*/
function del(key, cb) {
return type(key) != 'array'
? localForage.removeItem(key).then(wrap(cb), cb)
: Promise.all(key.map(del)).then(wrap(cb), cb);
}
/**
* Clear.
*
* @param {Function} cb
*/
function clear(cb) {
return localForage.clear().then(wrap(cb), cb);
}
/**
* Count records.
*
* @param {Functionc} cb
*/
function count(cb) {
return localForage.length().then(wrap(cb, true), cb);
}
/**
* Wrap promise style response to callback style.
* If `cb` does not specified, it uses console.log in development mode.
*
* @param {Function} cb
* @param {Boolean} [hasResult]
* @return {Function}
*/
function wrap(cb, hasResult) {
return function(res) {
if (type(cb) == 'function') {
hasResult ? cb(null, res) : cb();
} else if (hasResult && storage.development) {
console.log(res);
}
return res;
};
}
},{"component-type":3,"localforage":7,"promise":10}],2:[function(require,module,exports){
// shim for using process in browser
var process = module.exports = {};
var queue = [];
var draining = false;
function drainQueue() {
if (draining) {
return;
}
draining = true;
var currentQueue;
var len = queue.length;
while(len) {
currentQueue = queue;
queue = [];
var i = -1;
while (++i < len) {
currentQueue[i]();
}
len = queue.length;
}
draining = false;
}
process.nextTick = function (fun) {
queue.push(fun);
if (!draining) {
setTimeout(drainQueue, 0);
}
};
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues
process.versions = {};
function noop() {}
process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;
process.binding = function (name) {
throw new Error('process.binding is not supported');
};
// TODO(shtylman)
process.cwd = function () { return '/' };
process.chdir = function (dir) {
throw new Error('process.chdir is not supported');
};
process.umask = function() { return 0; };
},{}],3:[function(require,module,exports){
/**
* toString ref.
*/
var toString = Object.prototype.toString;
/**
* Return the type of `val`.
*
* @param {Mixed} val
* @return {String}
* @api public
*/
module.exports = function(val){
switch (toString.call(val)) {
case '[object Date]': return 'date';
case '[object RegExp]': return 'regexp';
case '[object Arguments]': return 'arguments';
case '[object Array]': return 'array';
case '[object Error]': return 'error';
}
if (val === null) return 'null';
if (val === undefined) return 'undefined';
if (val !== val) return 'nan';
if (val && val.nodeType === 1) return 'element';
val = val.valueOf
? val.valueOf()
: Object.prototype.valueOf.apply(val)
return typeof val;
};
},{}],4:[function(require,module,exports){
// Some code originally from async_storage.js in
// [Gaia](https://github.com/mozilla-b2g/gaia).
(function() {
'use strict';
// Originally found in https://github.com/mozilla-b2g/gaia/blob/e8f624e4cc9ea945727278039b3bc9bcb9f8667a/shared/js/async_storage.js
// Promises!
var Promise = (typeof module !== 'undefined' && module.exports) ?
require('promise') : this.Promise;
// Initialize IndexedDB; fall back to vendor-prefixed versions if needed.
var indexedDB = indexedDB || this.indexedDB || this.webkitIndexedDB ||
this.mozIndexedDB || this.OIndexedDB ||
this.msIndexedDB;
// If IndexedDB isn't available, we get outta here!
if (!indexedDB) {
return;
}
// Open the IndexedDB database (automatically creates one if one didn't
// previously exist), using any options set in the config.
function _initStorage(options) {
var self = this;
var dbInfo = {
db: null
};
if (options) {
for (var i in options) {
dbInfo[i] = options[i];
}
}
return new Promise(function(resolve, reject) {
var openreq = indexedDB.open(dbInfo.name, dbInfo.version);
openreq.onerror = function() {
reject(openreq.error);
};
openreq.onupgradeneeded = function() {
// First time setup: create an empty object store
openreq.result.createObjectStore(dbInfo.storeName);
};
openreq.onsuccess = function() {
dbInfo.db = openreq.result;
self._dbInfo = dbInfo;
resolve();
};
});
}
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') {
window.console.warn(key +
' used as a key, but it is not a string.');
key = String(key);
}
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
.objectStore(dbInfo.storeName);
var req = store.get(key);
req.onsuccess = function() {
var value = req.result;
if (value === undefined) {
value = null;
}
resolve(value);
};
req.onerror = function() {
reject(req.error);
};
}).catch(reject);
});
executeDeferedCallback(promise, callback);
return promise;
}
// Iterate over all items stored in database.
function iterate(iterator, callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
.objectStore(dbInfo.storeName);
var req = store.openCursor();
var iterationNumber = 1;
req.onsuccess = function() {
var cursor = req.result;
if (cursor) {
var result = iterator(cursor.value, cursor.key, iterationNumber++);
if (result !== void(0)) {
resolve(result);
} else {
cursor.continue();
}
} else {
resolve();
}
};
req.onerror = function() {
reject(req.error);
};
}).catch(reject);
});
executeDeferedCallback(promise, callback);
return promise;
}
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') {
window.console.warn(key +
' used as a key, but it is not a string.');
key = String(key);
}
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
var store = transaction.objectStore(dbInfo.storeName);
// The reason we don't _save_ null is because IE 10 does
// not support saving the `null` type in IndexedDB. How
// ironic, given the bug below!
// See: https://github.com/mozilla/localForage/issues/161
if (value === null) {
value = undefined;
}
var req = store.put(value, key);
transaction.oncomplete = function() {
// Cast to undefined so the value passed to
// callback/promise is the same as what one would get out
// of `getItem()` later. This leads to some weirdness
// (setItem('foo', undefined) will return `null`), but
// it's not my fault localStorage is our baseline and that
// it's weird.
if (value === undefined) {
value = null;
}
resolve(value);
};
transaction.onabort = transaction.onerror = function() {
reject(req.error);
};
}).catch(reject);
});
executeDeferedCallback(promise, callback);
return promise;
}
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') {
window.console.warn(key +
' used as a key, but it is not a string.');
key = String(key);
}
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
var store = transaction.objectStore(dbInfo.storeName);
// We use a Grunt task to make this safe for IE and some
// versions of Android (including those used by Cordova).
// Normally IE won't like `.delete()` and will insist on
// using `['delete']()`, but we have a build step that
// fixes this for us now.
var req = store.delete(key);
transaction.oncomplete = function() {
resolve();
};
transaction.onerror = function() {
reject(req.error);
};
// The request will be aborted if we've exceeded our storage
// space. In this case, we will reject with a specific
// "QuotaExceededError".
transaction.onabort = function(event) {
var error = event.target.error;
if (error === 'QuotaExceededError') {
reject(error);
}
};
}).catch(reject);
});
executeDeferedCallback(promise, callback);
return promise;
}
function clear(callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
var store = transaction.objectStore(dbInfo.storeName);
var req = store.clear();
transaction.oncomplete = function() {
resolve();
};
transaction.onabort = transaction.onerror = function() {
reject(req.error);
};
}).catch(reject);
});
executeDeferedCallback(promise, callback);
return promise;
}
function length(callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
.objectStore(dbInfo.storeName);
var req = store.count();
req.onsuccess = function() {
resolve(req.result);
};
req.onerror = function() {
reject(req.error);
};
}).catch(reject);
});
executeCallback(promise, callback);
return promise;
}
function key(n, callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
if (n < 0) {
resolve(null);
return;
}
self.ready().then(function() {
var dbInfo = self._dbInfo;
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
.objectStore(dbInfo.storeName);
var advanced = false;
var req = store.openCursor();
req.onsuccess = function() {
var cursor = req.result;
if (!cursor) {
// this means there weren't enough keys
resolve(null);
return;
}
if (n === 0) {
// We have the first key, return it if that's what they
// wanted.
resolve(cursor.key);
} else {
if (!advanced) {
// Otherwise, ask the cursor to skip ahead n
// records.
advanced = true;
cursor.advance(n);
} else {
// When we get here, we've got the nth key.
resolve(cursor.key);
}
}
};
req.onerror = function() {
reject(req.error);
};
}).catch(reject);
});
executeCallback(promise, callback);
return promise;
}
function keys(callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
.objectStore(dbInfo.storeName);
var req = store.openCursor();
var keys = [];
req.onsuccess = function() {
var cursor = req.result;
if (!cursor) {
resolve(keys);
return;
}
keys.push(cursor.key);
cursor.continue();
};
req.onerror = function() {
reject(req.error);
};
}).catch(reject);
});
executeCallback(promise, callback);
return promise;
}
function executeCallback(promise, callback) {
if (callback) {
promise.then(function(result) {
callback(null, result);
}, function(error) {
callback(error);
});
}
}
function executeDeferedCallback(promise, callback) {
if (callback) {
promise.then(function(result) {
deferCallback(callback, result);
}, function(error) {
callback(error);
});
}
}
// Under Chrome the callback is called before the changes (save, clear)
// are actually made. So we use a defer function which wait that the
// call stack to be empty.
// For more info : https://github.com/mozilla/localForage/issues/175
// Pull request : https://github.com/mozilla/localForage/pull/178
function deferCallback(callback, result) {
if (callback) {
return setTimeout(function() {
return callback(null, result);
}, 0);
}
}
var asyncStorage = {
_driver: 'asyncStorage',
_initStorage: _initStorage,
iterate: iterate,
getItem: getItem,
setItem: setItem,
removeItem: removeItem,
clear: clear,
length: length,
key: key,
keys: keys
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = asyncStorage;
} else if (typeof define === 'function' && define.amd) {
define('asyncStorage', function() {
return asyncStorage;
});
} else {
this.asyncStorage = asyncStorage;
}
}).call(window);
},{"promise":10}],5:[function(require,module,exports){
// 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).
(function() {
'use strict';
// Promises!
var Promise = (typeof module !== 'undefined' && module.exports) ?
require('promise') : this.Promise;
var globalObject = this;
var serializer = null;
var localStorage = null;
// If the app is running inside a Google Chrome packaged webapp, or some
// other context where localStorage isn't available, we don't use
// localStorage. This feature detection is preferred over the old
// `if (window.chrome && window.chrome.runtime)` code.
// See: https://github.com/mozilla/localForage/issues/68
try {
// If localStorage isn't available, we get outta here!
// This should be inside a try catch
if (!this.localStorage || !('setItem' in this.localStorage)) {
return;
}
// Initialize localStorage and create a variable to use throughout
// the code.
localStorage = this.localStorage;
} catch (e) {
return;
}
var ModuleType = {
DEFINE: 1,
EXPORT: 2,
WINDOW: 3
};
// Attaching to window (i.e. no module loader) is the assumed,
// simple default.
var moduleType = ModuleType.WINDOW;
// Find out what kind of module setup we have; if none, we'll just attach
// localForage to the main window.
if (typeof module !== 'undefined' && module.exports) {
moduleType = ModuleType.EXPORT;
} else if (typeof define === 'function' && define.amd) {
moduleType = ModuleType.DEFINE;
}
// 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 + '/';
self._dbInfo = dbInfo;
var serializerPromise = new Promise(function(resolve/*, reject*/) {
// We allow localForage to be declared as a module or as a
// library available without AMD/require.js.
if (moduleType === ModuleType.DEFINE) {
require(['localforageSerializer'], resolve);
} else if (moduleType === ModuleType.EXPORT) {
// Making it browserify friendly
resolve(require('./../utils/serializer'));
} else {
resolve(globalObject.localforageSerializer);
}
});
return serializerPromise.then(function(lib) {
serializer = lib;
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') {
window.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 = 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 keyPrefix = self._dbInfo.keyPrefix;
var keyPrefixLength = keyPrefix.length;
var length = localStorage.length;
for (var i = 0; i < length; i++) {
var key = localStorage.key(i);
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 = serializer.deserialize(value);
}
value = iterator(value, key.substring(keyPrefixLength), i + 1);
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') {
window.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') {
window.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) {
serializer.serialize(value, function(value, error) {
if (error) {
reject(error);
} else {
try {
var dbInfo = self._dbInfo;
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;
}
function executeCallback(promise, callback) {
if (callback) {
promise.then(function(result) {
callback(null, result);
}, function(error) {
callback(error);
});
}
}
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
};
if (moduleType === ModuleType.EXPORT) {
module.exports = localStorageWrapper;
} else if (moduleType === ModuleType.DEFINE) {
define('localStorageWrapper', function() {
return localStorageWrapper;
});
} else {
this.localStorageWrapper = localStorageWrapper;
}
}).call(window);
},{"./../utils/serializer":8,"promise":10}],6:[function(require,module,exports){
/*
* Includes code from:
*
* base64-arraybuffer
* https://github.com/niklasvh/base64-arraybuffer
*
* Copyright (c) 2012 Niklas von Hertzen
* Licensed under the MIT license.
*/
(function() {
'use strict';
// Promises!
var Promise = (typeof module !== 'undefined' && module.exports) ?
require('promise') : this.Promise;
var globalObject = this;
var serializer = null;
var openDatabase = this.openDatabase;
// If WebSQL methods aren't available, we can stop now.
if (!openDatabase) {
return;
}
var ModuleType = {
DEFINE: 1,
EXPORT: 2,
WINDOW: 3
};
// Attaching to window (i.e. no module loader) is the assumed,
// simple default.
var moduleType = ModuleType.WINDOW;
// Find out what kind of module setup we have; if none, we'll just attach
// localForage to the main window.
if (typeof module !== 'undefined' && module.exports) {
moduleType = ModuleType.EXPORT;
} else if (typeof define === 'function' && define.amd) {
moduleType = ModuleType.DEFINE;
}
// Open the WebSQL database (automatically creates one if one didn't
// previously exist), using any options set in the config.
function _initStorage(options) {
var self = this;
var dbInfo = {
db: null
};
if (options) {
for (var i in options) {
dbInfo[i] = typeof(options[i]) !== 'string' ?
options[i].toString() : options[i];
}
}
var serializerPromise = new Promise(function(resolve/*, reject*/) {
// We allow localForage to be declared as a module or as a
// library available without AMD/require.js.
if (moduleType === ModuleType.DEFINE) {
require(['localforageSerializer'], resolve);
} else if (moduleType === ModuleType.EXPORT) {
// Making it browserify friendly
resolve(require('./../utils/serializer'));
} else {
resolve(globalObject.localforageSerializer);
}
});
var dbInfoPromise = new Promise(function(resolve, reject) {
// Open the database; the openDatabase API will automatically
// create it for us if it doesn't exist.
try {
dbInfo.db = openDatabase(dbInfo.name, String(dbInfo.version),
dbInfo.description, dbInfo.size);
} catch (e) {
return self.setDriver(self.LOCALSTORAGE).then(function() {
return self._initStorage(options);
}).then(resolve).catch(reject);
}
// Create our key/value table if it doesn't exist.
dbInfo.db.transaction(function(t) {
t.executeSql('CREATE TABLE IF NOT EXISTS ' + dbInfo.storeName +
' (id INTEGER PRIMARY KEY, key unique, value)', [],
function() {
self._dbInfo = dbInfo;
resolve();
}, function(t, error) {
reject(error);
});
});
});
return serializerPromise.then(function(lib) {
serializer = lib;
return dbInfoPromise;
});
}
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') {
window.console.warn(key +
' used as a key, but it is not a string.');
key = String(key);
}
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
dbInfo.db.transaction(function(t) {
t.executeSql('SELECT * FROM ' + dbInfo.storeName +
' WHERE key = ? LIMIT 1', [key],
function(t, results) {
var result = results.rows.length ?
results.rows.item(0).value : null;
// Check to see if this is serialized content we need to
// unpack.
if (result) {
result = serializer.deserialize(result);
}
resolve(result);
}, function(t, error) {
reject(error);
});
});
}).catch(reject);
});
executeCallback(promise, callback);
return promise;
}
function iterate(iterator, callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
dbInfo.db.transaction(function(t) {
t.executeSql('SELECT * FROM ' + dbInfo.storeName, [],
function(t, results) {
var rows = results.rows;
var length = rows.length;
for (var i = 0; i < length; i++) {
var item = rows.item(i);
var result = item.value;
// Check to see if this is serialized content
// we need to unpack.
if (result) {
result = serializer.deserialize(result);
}
result = iterator(result, item.key, i + 1);
// void(0) prevents problems with redefinition
// of `undefined`.
if (result !== void(0)) {
resolve(result);
return;
}
}
resolve();
}, function(t, error) {
reject(error);
});
});
}).catch(reject);
});
executeCallback(promise, callback);
return promise;
}
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') {
window.console.warn(key +
' used as a key, but it is not a string.');
key = String(key);
}
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
// The localStorage API doesn't return undefined values in an
// "expected" way, so undefined is always cast to null in all
// drivers. See: https://github.com/mozilla/localForage/pull/42
if (value === undefined) {
value = null;
}
// Save the original value to pass to the callback.
var originalValue = value;
serializer.serialize(value, function(value, error) {
if (error) {
reject(error);
} else {
var dbInfo = self._dbInfo;
dbInfo.db.transaction(function(t) {
t.executeSql('INSERT OR REPLACE INTO ' +
dbInfo.storeName +
' (key, value) VALUES (?, ?)',
[key, value], function() {
resolve(originalValue);
}, function(t, error) {
reject(error);
});
}, function(sqlError) { // The transaction failed; check
// to see if it's a quota error.
if (sqlError.code === sqlError.QUOTA_ERR) {
// We reject the callback outright for now, but
// it's worth trying to re-run the transaction.
// Even if the user accepts the prompt to use
// more storage on Safari, this error will
// be called.
//
// TODO: Try to re-run the transaction.
reject(sqlError);
}
});
}
});
}).catch(reject);
});
executeCallback(promise, callback);
return promise;
}
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') {
window.console.warn(key +
' used as a key, but it is not a string.');
key = String(key);
}
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
dbInfo.db.transaction(function(t) {
t.executeSql('DELETE FROM ' + dbInfo.storeName +
' WHERE key = ?', [key], function() {
resolve();
}, function(t, error) {
reject(error);
});
});
}).catch(reject);
});
executeCallback(promise, callback);
return promise;
}
// Deletes every item in the table.
// TODO: Find out if this resets the AUTO_INCREMENT number.
function clear(callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
dbInfo.db.transaction(function(t) {
t.executeSql('DELETE FROM ' + dbInfo.storeName, [],
function() {
resolve();
}, function(t, error) {
reject(error);
});
});
}).catch(reject);
});
executeCallback(promise, callback);
return promise;
}
// Does a simple `COUNT(key)` to get the number of items stored in
// localForage.
function length(callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
dbInfo.db.transaction(function(t) {
// Ahhh, SQL makes this one soooooo easy.
t.executeSql('SELECT COUNT(key) as c FROM ' +
dbInfo.storeName, [], function(t, results) {
var result = results.rows.item(0).c;
resolve(result);
}, function(t, error) {
reject(error);
});
});
}).catch(reject);
});
executeCallback(promise, callback);
return promise;
}
// Return the key located at key index X; essentially gets the key from a
// `WHERE id = ?`. This is the most efficient way I can think to implement
// this rarely-used (in my experience) part of the API, but it can seem
// inconsistent, because we do `INSERT OR REPLACE INTO` on `setItem()`, so
// the ID of each key will change every time it's updated. Perhaps a stored
// procedure for the `setItem()` SQL would solve this problem?
// TODO: Don't change ID on `setItem()`.
function key(n, callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
dbInfo.db.transaction(function(t) {
t.executeSql('SELECT key FROM ' + dbInfo.storeName +
' WHERE id = ? LIMIT 1', [n + 1],
function(t, results) {
var result = results.rows.length ?
results.rows.item(0).key : null;
resolve(result);
}, function(t, error) {
reject(error);
});
});
}).catch(reject);
});
executeCallback(promise, callback);
return promise;
}
function keys(callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
dbInfo.db.transaction(function(t) {
t.executeSql('SELECT key FROM ' + dbInfo.storeName, [],
function(t, results) {
var keys = [];
for (var i = 0; i < results.rows.length; i++) {
keys.push(results.rows.item(i).key);
}
resolve(keys);
}, function(t, error) {
reject(error);
});
});
}).catch(reject);
});
executeCallback(promise, callback);
return promise;
}
function executeCallback(promise, callback) {
if (callback) {
promise.then(function(result) {
callback(null, result);
}, function(error) {
callback(error);
});
}
}
var webSQLStorage = {
_driver: 'webSQLStorage',
_initStorage: _initStorage,
iterate: iterate,
getItem: getItem,
setItem: setItem,
removeItem: removeItem,
clear: clear,
length: length,
key: key,
keys: keys
};
if (moduleType === ModuleType.DEFINE) {
define('webSQLStorage', function() {
return webSQLStorage;
});
} else if (moduleType === ModuleType.EXPORT) {
module.exports = webSQLStorage;
} else {
this.webSQLStorage = webSQLStorage;
}
}).call(window);
},{"./../utils/serializer":8,"promise":10}],7:[function(require,module,exports){
(function() {
'use strict';
// Promises!
var Promise = (typeof module !== 'undefined' && module.exports) ?
require('promise') : this.Promise;
// Custom drivers are stored here when `defineDriver()` is called.
// They are shared across all instances of localForage.
var CustomDrivers = {};
var DriverType = {
INDEXEDDB: 'asyncStorage',
LOCALSTORAGE: 'localStorageWrapper',
WEBSQL: 'webSQLStorage'
};
var DefaultDriverOrder = [
DriverType.INDEXEDDB,
DriverType.WEBSQL,
DriverType.LOCALSTORAGE
];
var LibraryMethods = [
'clear',
'getItem',
'iterate',
'key',
'keys',
'length',
'removeItem',
'setItem'
];
var ModuleType = {
DEFINE: 1,
EXPORT: 2,
WINDOW: 3
};
var DefaultConfig = {
description: '',
driver: DefaultDriverOrder.slice(),
name: 'localforage',
// Default DB size is _JUST UNDER_ 5MB, as it's the highest size
// we can use without a prompt.
size: 4980736,
storeName: 'keyvaluepairs',
version: 1.0
};
// Attaching to window (i.e. no module loader) is the assumed,
// simple default.
var moduleType = ModuleType.WINDOW;
// Find out what kind of module setup we have; if none, we'll just attach
// localForage to the main window.
if (typeof module !== 'undefined' && module.exports) {
moduleType = ModuleType.EXPORT;
} else if (typeof define === 'function' && define.amd) {
moduleType = ModuleType.DEFINE;
}
// Check to see if IndexedDB is available and if it is the latest
// implementation; it's our preferred backend library. We use "_spec_test"
// as the name of the database because it's not the one we'll operate on,
// but it's useful to make sure its using the right spec.
// See: https://github.com/mozilla/localForage/issues/128
var driverSupport = (function(self) {
// Initialize IndexedDB; fall back to vendor-prefixed versions
// if needed.
var indexedDB = indexedDB || self.indexedDB || self.webkitIndexedDB ||
self.mozIndexedDB || self.OIndexedDB ||
self.msIndexedDB;
var result = {};
result[DriverType.WEBSQL] = !!self.openDatabase;
result[DriverType.INDEXEDDB] = !!(function() {
// We mimic PouchDB here; just UA test for Safari (which, as of
// iOS 8/Yosemite, doesn't properly support IndexedDB).
// IndexedDB support is broken and different from Blink's.
// This is faster than the test case (and it's sync), so we just
// do this. *SIGH*
// http://bl.ocks.org/nolanlawson/raw/c83e9039edf2278047e9/
//
// We test for openDatabase because IE Mobile identifies itself
// as Safari. Oh the lulz...
if (typeof self.openDatabase !== 'undefined' && self.navigator &&
self.navigator.userAgent &&
/Safari/.test(self.navigator.userAgent) &&
!/Chrome/.test(self.navigator.userAgent)) {
return false;
}
try {
return indexedDB &&
typeof indexedDB.open === 'function' &&
// Some Samsung/HTC Android 4.0-4.3 devices
// have older IndexedDB specs; if this isn't available
// their IndexedDB is too old for us to use.
// (Replaces the onupgradeneeded test.)
typeof self.IDBKeyRange !== 'undefined';
} catch (e) {
return false;
}
})();
result[DriverType.LOCALSTORAGE] = !!(function() {
try {
return (self.localStorage &&
('setItem' in self.localStorage) &&
(self.localStorage.setItem));
} catch (e) {
return false;
}
})();
return result;
})(this);
var isArray = Array.isArray || function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
function callWhenReady(localForageInstance, libraryMethod) {
localForageInstance[libraryMethod] = function() {
var _args = arguments;
return localForageInstance.ready().then(function() {
return localForageInstance[libraryMethod].apply(localForageInstance, _args);
});