UNPKG

asyncstorage

Version:

Asynchronous browser storage with multiple back-ends (IndexedDB, WebSQL, localStorage)

1,537 lines (1,298 loc) 79.8 kB
(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); });