(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= 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); }); }; } function extend() { for (var i = 1; i < arguments.length; i++) { var arg = arguments[i]; if (arg) { for (var key in arg) { if (arg.hasOwnProperty(key)) { if (isArray(arg[key])) { arguments[0][key] = arg[key].slice(); } else { arguments[0][key] = arg[key]; } } } } } return arguments[0]; } function isLibraryDriver(driverName) { for (var driver in DriverType) { if (DriverType.hasOwnProperty(driver) && DriverType[driver] === driverName) { return true; } } return false; } var globalObject = this; function LocalForage(options) { this._config = extend({}, DefaultConfig, options); this._driverSet = null; this._ready = false; this._dbInfo = null; // Add a stub for each driver API method that delays the call to the // corresponding driver method until localForage is ready. These stubs // will be replaced by the driver methods as soon as the driver is // loaded, so there is no performance impact. for (var i = 0; i < LibraryMethods.length; i++) { callWhenReady(this, LibraryMethods[i]); } this.setDriver(this._config.driver); } LocalForage.prototype.INDEXEDDB = DriverType.INDEXEDDB; LocalForage.prototype.LOCALSTORAGE = DriverType.LOCALSTORAGE; LocalForage.prototype.WEBSQL = DriverType.WEBSQL; // Set any config values for localForage; can be called anytime before // the first API call (e.g. `getItem`, `setItem`). // We loop through options so we don't overwrite existing config // values. LocalForage.prototype.config = function(options) { // If the options argument is an object, we use it to set values. // Otherwise, we return either a specified config value or all // config values. if (typeof(options) === 'object') { // If localforage is ready and fully initialized, we can't set // any new configuration values. Instead, we return an error. if (this._ready) { return new Error("Can't call config() after localforage " + 'has been used.'); } for (var i in options) { if (i === 'storeName') { options[i] = options[i].replace(/\W/g, '_'); } this._config[i] = options[i]; } // after all config options are set and // the driver option is used, try setting it if ('driver' in options && options.driver) { this.setDriver(this._config.driver); } return true; } else if (typeof(options) === 'string') { return this._config[options]; } else { return this._config; } }; // Used to define a custom driver, shared across all instances of // localForage. LocalForage.prototype.defineDriver = function(driverObject, callback, errorCallback) { var defineDriver = new Promise(function(resolve, reject) { try { var driverName = driverObject._driver; var complianceError = new Error( 'Custom driver not compliant; see ' + 'https://mozilla.github.io/localForage/#definedriver' ); var namingError = new Error( 'Custom driver name already in use: ' + driverObject._driver ); // A driver name should be defined and not overlap with the // library-defined, default drivers. if (!driverObject._driver) { reject(complianceError); return; } if (isLibraryDriver(driverObject._driver)) { reject(namingError); return; } var customDriverMethods = LibraryMethods.concat('_initStorage'); for (var i = 0; i < customDriverMethods.length; i++) { var customDriverMethod = customDriverMethods[i]; if (!customDriverMethod || !driverObject[customDriverMethod] || typeof driverObject[customDriverMethod] !== 'function') { reject(complianceError); return; } } var supportPromise = Promise.resolve(true); if ('_support' in driverObject) { if (driverObject._support && typeof driverObject._support === 'function') { supportPromise = driverObject._support(); } else { supportPromise = Promise.resolve(!!driverObject._support); } } supportPromise.then(function(supportResult) { driverSupport[driverName] = supportResult; CustomDrivers[driverName] = driverObject; resolve(); }, reject); } catch (e) { reject(e); } }); defineDriver.then(callback, errorCallback); return defineDriver; }; LocalForage.prototype.driver = function() { return this._driver || null; }; LocalForage.prototype.ready = function(callback) { var self = this; var ready = new Promise(function(resolve, reject) { self._driverSet.then(function() { if (self._ready === null) { self._ready = self._initStorage(self._config); } self._ready.then(resolve, reject); }).catch(reject); }); ready.then(callback, callback); return ready; }; LocalForage.prototype.setDriver = function(drivers, callback, errorCallback) { var self = this; if (typeof drivers === 'string') { drivers = [drivers]; } this._driverSet = new Promise(function(resolve, reject) { var driverName = self._getFirstSupportedDriver(drivers); var error = new Error('No available storage method found.'); if (!driverName) { self._driverSet = Promise.reject(error); reject(error); return; } self._dbInfo = null; self._ready = null; if (isLibraryDriver(driverName)) { // We allow localForage to be declared as a module or as a // library available without AMD/require.js. if (moduleType === ModuleType.DEFINE) { require([driverName], function(lib) { self._extend(lib); resolve(); }); return; } else if (moduleType === ModuleType.EXPORT) { // Making it browserify friendly var driver; switch (driverName) { case self.INDEXEDDB: driver = require('./drivers/indexeddb'); break; case self.LOCALSTORAGE: driver = require('./drivers/localstorage'); break; case self.WEBSQL: driver = require('./drivers/websql'); } self._extend(driver); } else { self._extend(globalObject[driverName]); } } else if (CustomDrivers[driverName]) { self._extend(CustomDrivers[driverName]); } else { self._driverSet = Promise.reject(error); reject(error); return; } resolve(); }); function setDriverToConfig() { self._config.driver = self.driver(); } this._driverSet.then(setDriverToConfig, setDriverToConfig); this._driverSet.then(callback, errorCallback); return this._driverSet; }; LocalForage.prototype.supports = function(driverName) { return !!driverSupport[driverName]; }; LocalForage.prototype._extend = function(libraryMethodsAndProperties) { extend(this, libraryMethodsAndProperties); }; // Used to determine which driver we should use as the backend for this // instance of localForage. LocalForage.prototype._getFirstSupportedDriver = function(drivers) { if (drivers && isArray(drivers)) { for (var i = 0; i < drivers.length; i++) { var driver = drivers[i]; if (this.supports(driver)) { return driver; } } } return null; }; LocalForage.prototype.createInstance = function(options) { return new LocalForage(options); }; // The actual localForage object that we expose as a module or via a // global. It's extended by pulling in one of our other libraries. var localForage = new LocalForage(); // We allow localForage to be declared as a module or as a library // available without AMD/require.js. if (moduleType === ModuleType.DEFINE) { define('localforage', function() { return localForage; }); } else if (moduleType === ModuleType.EXPORT) { module.exports = localForage; } else { this.localforage = localForage; } }).call(window); },{"./drivers/indexeddb":4,"./drivers/localstorage":5,"./drivers/websql":6,"promise":10}],8:[function(require,module,exports){ (function() { 'use strict'; // Sadly, the best way to save binary data in WebSQL/localStorage is serializing // it to Base64, so this is how we store it to prevent very strange errors with less // verbose ways of binary <-> string data storage. var BASE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; var SERIALIZED_MARKER = '__lfsc__:'; var SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER.length; // OMG the serializations! var TYPE_ARRAYBUFFER = 'arbf'; var TYPE_BLOB = 'blob'; var TYPE_INT8ARRAY = 'si08'; var TYPE_UINT8ARRAY = 'ui08'; var TYPE_UINT8CLAMPEDARRAY = 'uic8'; var TYPE_INT16ARRAY = 'si16'; var TYPE_INT32ARRAY = 'si32'; var TYPE_UINT16ARRAY = 'ur16'; var TYPE_UINT32ARRAY = 'ui32'; var TYPE_FLOAT32ARRAY = 'fl32'; var TYPE_FLOAT64ARRAY = 'fl64'; var TYPE_SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER_LENGTH + TYPE_ARRAYBUFFER.length; // Serialize a value, afterwards executing a callback (which usually // instructs the `setItem()` callback/promise to be executed). This is how // we store binary data with localStorage. function serialize(value, callback) { var valueString = ''; if (value) { valueString = value.toString(); } // Cannot use `value instanceof ArrayBuffer` or such here, as these // checks fail when running the tests using casper.js... // // TODO: See why those tests fail and use a better solution. if (value && (value.toString() === '[object ArrayBuffer]' || value.buffer && value.buffer.toString() === '[object ArrayBuffer]')) { // Convert binary arrays to a string and prefix the string with // a special marker. var buffer; var marker = SERIALIZED_MARKER; if (value instanceof ArrayBuffer) { buffer = value; marker += TYPE_ARRAYBUFFER; } else { buffer = value.buffer; if (valueString === '[object Int8Array]') { marker += TYPE_INT8ARRAY; } else if (valueString === '[object Uint8Array]') { marker += TYPE_UINT8ARRAY; } else if (valueString === '[object Uint8ClampedArray]') { marker += TYPE_UINT8CLAMPEDARRAY; } else if (valueString === '[object Int16Array]') { marker += TYPE_INT16ARRAY; } else if (valueString === '[object Uint16Array]') { marker += TYPE_UINT16ARRAY; } else if (valueString === '[object Int32Array]') { marker += TYPE_INT32ARRAY; } else if (valueString === '[object Uint32Array]') { marker += TYPE_UINT32ARRAY; } else if (valueString === '[object Float32Array]') { marker += TYPE_FLOAT32ARRAY; } else if (valueString === '[object Float64Array]') { marker += TYPE_FLOAT64ARRAY; } else { callback(new Error('Failed to get type for BinaryArray')); } } callback(marker + bufferToString(buffer)); } else if (valueString === '[object Blob]') { // Conver the blob to a binaryArray and then to a string. var fileReader = new FileReader(); fileReader.onload = function() { var str = bufferToString(this.result); callback(SERIALIZED_MARKER + TYPE_BLOB + str); }; fileReader.readAsArrayBuffer(value); } else { try { callback(JSON.stringify(value)); } catch (e) { window.console.error("Couldn't convert value into a JSON " + 'string: ', value); callback(null, e); } } } // Deserialize data we've inserted into a value column/field. We place // special markers into our strings to mark them as encoded; this isn't // as nice as a meta field, but it's the only sane thing we can do whilst // keeping localStorage support intact. // // Oftentimes this will just deserialize JSON content, but if we have a // special marker (SERIALIZED_MARKER, defined above), we will extract // some kind of arraybuffer/binary data/typed array out of the string. function deserialize(value) { // If we haven't marked this string as being specially serialized (i.e. // something other than serialized JSON), we can just return it and be // done with it. if (value.substring(0, SERIALIZED_MARKER_LENGTH) !== SERIALIZED_MARKER) { return JSON.parse(value); } // The following code deals with deserializing some kind of Blob or // TypedArray. First we separate out the type of data we're dealing // with from the data itself. var serializedString = value.substring(TYPE_SERIALIZED_MARKER_LENGTH); var type = value.substring(SERIALIZED_MARKER_LENGTH, TYPE_SERIALIZED_MARKER_LENGTH); var buffer = stringToBuffer(serializedString); // Return the right type based on the code/type set during // serialization. switch (type) { case TYPE_ARRAYBUFFER: return buffer; case TYPE_BLOB: return new Blob([buffer]); case TYPE_INT8ARRAY: return new Int8Array(buffer); case TYPE_UINT8ARRAY: return new Uint8Array(buffer); case TYPE_UINT8CLAMPEDARRAY: return new Uint8ClampedArray(buffer); case TYPE_INT16ARRAY: return new Int16Array(buffer); case TYPE_UINT16ARRAY: return new Uint16Array(buffer); case TYPE_INT32ARRAY: return new Int32Array(buffer); case TYPE_UINT32ARRAY: return new Uint32Array(buffer); case TYPE_FLOAT32ARRAY: return new Float32Array(buffer); case TYPE_FLOAT64ARRAY: return new Float64Array(buffer); default: throw new Error('Unkown type: ' + type); } } function stringToBuffer(serializedString) { // Fill the string into a ArrayBuffer. var bufferLength = serializedString.length * 0.75; var len = serializedString.length; var i; var p = 0; var encoded1, encoded2, encoded3, encoded4; if (serializedString[serializedString.length - 1] === '=') { bufferLength--; if (serializedString[serializedString.length - 2] === '=') { bufferLength--; } } var buffer = new ArrayBuffer(bufferLength); var bytes = new Uint8Array(buffer); for (i = 0; i < len; i+=4) { encoded1 = BASE_CHARS.indexOf(serializedString[i]); encoded2 = BASE_CHARS.indexOf(serializedString[i+1]); encoded3 = BASE_CHARS.indexOf(serializedString[i+2]); encoded4 = BASE_CHARS.indexOf(serializedString[i+3]); /*jslint bitwise: true */ bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); } return buffer; } // Converts a buffer to a string to store, serialized, in the backend // storage library. function bufferToString(buffer) { // base64-arraybuffer var bytes = new Uint8Array(buffer); var base64String = ''; var i; for (i = 0; i < bytes.length; i += 3) { /*jslint bitwise: true */ base64String += BASE_CHARS[bytes[i] >> 2]; base64String += BASE_CHARS[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; base64String += BASE_CHARS[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; base64String += BASE_CHARS[bytes[i + 2] & 63]; } if ((bytes.length % 3) === 2) { base64String = base64String.substring(0, base64String.length - 1) + '='; } else if (bytes.length % 3 === 1) { base64String = base64String.substring(0, base64String.length - 2) + '=='; } return base64String; } var localforageSerializer = { serialize: serialize, deserialize: deserialize, stringToBuffer: stringToBuffer, bufferToString: bufferToString }; if (typeof module !== 'undefined' && module.exports) { module.exports = localforageSerializer; } else if (typeof define === 'function' && define.amd) { define('localforageSerializer', function() { return localforageSerializer; }); } else { this.localforageSerializer = localforageSerializer; } }).call(window); },{}],9:[function(require,module,exports){ 'use strict'; var asap = require('asap') module.exports = Promise function Promise(fn) { if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new') if (typeof fn !== 'function') throw new TypeError('not a function') var state = null var value = null var deferreds = [] var self = this this.then = function(onFulfilled, onRejected) { return new Promise(function(resolve, reject) { handle(new Handler(onFulfilled, onRejected, resolve, reject)) }) } function handle(deferred) { if (state === null) { deferreds.push(deferred) return } asap(function() { var cb = state ? deferred.onFulfilled : deferred.onRejected if (cb === null) { (state ? deferred.resolve : deferred.reject)(value) return } var ret try { ret = cb(value) } catch (e) { deferred.reject(e) return } deferred.resolve(ret) }) } function resolve(newValue) { try { //Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.') if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { var then = newValue.then if (typeof then === 'function') { doResolve(then.bind(newValue), resolve, reject) return } } state = true value = newValue finale() } catch (e) { reject(e) } } function reject(newValue) { state = false value = newValue finale() } function finale() { for (var i = 0, len = deferreds.length; i < len; i++) handle(deferreds[i]) deferreds = null } doResolve(fn, resolve, reject) } function Handler(onFulfilled, onRejected, resolve, reject){ this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null this.onRejected = typeof onRejected === 'function' ? onRejected : null this.resolve = resolve this.reject = reject } /** * Take a potentially misbehaving resolver function and make sure * onFulfilled and onRejected are only called once. * * Makes no guarantees about asynchrony. */ function doResolve(fn, onFulfilled, onRejected) { var done = false; try { fn(function (value) { if (done) return done = true onFulfilled(value) }, function (reason) { if (done) return done = true onRejected(reason) }) } catch (ex) { if (done) return done = true onRejected(ex) } } },{"asap":11}],10:[function(require,module,exports){ 'use strict'; //This file contains then/promise specific extensions to the core promise API var Promise = require('./core.js') var asap = require('asap') module.exports = Promise /* Static Functions */ function ValuePromise(value) { this.then = function (onFulfilled) { if (typeof onFulfilled !== 'function') return this return new Promise(function (resolve, reject) { asap(function () { try { resolve(onFulfilled(value)) } catch (ex) { reject(ex); } }) }) } } ValuePromise.prototype = Object.create(Promise.prototype) var TRUE = new ValuePromise(true) var FALSE = new ValuePromise(false) var NULL = new ValuePromise(null) var UNDEFINED = new ValuePromise(undefined) var ZERO = new ValuePromise(0) var EMPTYSTRING = new ValuePromise('') Promise.resolve = function (value) { if (value instanceof Promise) return value if (value === null) return NULL if (value === undefined) return UNDEFINED if (value === true) return TRUE if (value === false) return FALSE if (value === 0) return ZERO if (value === '') return EMPTYSTRING if (typeof value === 'object' || typeof value === 'function') { try { var then = value.then if (typeof then === 'function') { return new Promise(then.bind(value)) } } catch (ex) { return new Promise(function (resolve, reject) { reject(ex) }) } } return new ValuePromise(value) } Promise.from = Promise.cast = function (value) { var err = new Error('Promise.from and Promise.cast are deprecated, use Promise.resolve instead') err.name = 'Warning' console.warn(err.stack) return Promise.resolve(value) } Promise.denodeify = function (fn, argumentCount) { argumentCount = argumentCount || Infinity return function () { var self = this var args = Array.prototype.slice.call(arguments) return new Promise(function (resolve, reject) { while (args.length && args.length > argumentCount) { args.pop() } args.push(function (err, res) { if (err) reject(err) else resolve(res) }) fn.apply(self, args) }) } } Promise.nodeify = function (fn) { return function () { var args = Array.prototype.slice.call(arguments) var callback = typeof args[args.length - 1] === 'function' ? args.pop() : null try { return fn.apply(this, arguments).nodeify(callback) } catch (ex) { if (callback === null || typeof callback == 'undefined') { return new Promise(function (resolve, reject) { reject(ex) }) } else { asap(function () { callback(ex) }) } } } } Promise.all = function () { var calledWithArray = arguments.length === 1 && Array.isArray(arguments[0]) var args = Array.prototype.slice.call(calledWithArray ? arguments[0] : arguments) if (!calledWithArray) { var err = new Error('Promise.all should be called with a single array, calling it with multiple arguments is deprecated') err.name = 'Warning' console.warn(err.stack) } return new Promise(function (resolve, reject) { if (args.length === 0) return resolve([]) var remaining = args.length function res(i, val) { try { if (val && (typeof val === 'object' || typeof val === 'function')) { var then = val.then if (typeof then === 'function') { then.call(val, function (val) { res(i, val) }, reject) return } } args[i] = val if (--remaining === 0) { resolve(args); } } catch (ex) { reject(ex) } } for (var i = 0; i < args.length; i++) { res(i, args[i]) } }) } Promise.reject = function (value) { return new Promise(function (resolve, reject) { reject(value); }); } Promise.race = function (values) { return new Promise(function (resolve, reject) { values.forEach(function(value){ Promise.resolve(value).then(resolve, reject); }) }); } /* Prototype Methods */ Promise.prototype.done = function (onFulfilled, onRejected) { var self = arguments.length ? this.then.apply(this, arguments) : this self.then(null, function (err) { asap(function () { throw err }) }) } Promise.prototype.nodeify = function (callback) { if (typeof callback != 'function') return this this.then(function (value) { asap(function () { callback(null, value) }) }, function (err) { asap(function () { callback(err) }) }) } Promise.prototype['catch'] = function (onRejected) { return this.then(null, onRejected); } },{"./core.js":9,"asap":11}],11:[function(require,module,exports){ (function (process){ // Use the fastest possible means to execute a task in a future turn // of the event loop. // linked list of tasks (single, with head node) var head = {task: void 0, next: null}; var tail = head; var flushing = false; var requestFlush = void 0; var isNodeJS = false; function flush() { /* jshint loopfunc: true */ while (head.next) { head = head.next; var task = head.task; head.task = void 0; var domain = head.domain; if (domain) { head.domain = void 0; domain.enter(); } try { task(); } catch (e) { if (isNodeJS) { // In node, uncaught exceptions are considered fatal errors. // Re-throw them synchronously to interrupt flushing! // Ensure continuation if the uncaught exception is suppressed // listening "uncaughtException" events (as domains does). // Continue in next event to avoid tick recursion. if (domain) { domain.exit(); } setTimeout(flush, 0); if (domain) { domain.enter(); } throw e; } else { // In browsers, uncaught exceptions are not fatal. // Re-throw them asynchronously to avoid slow-downs. setTimeout(function() { throw e; }, 0); } } if (domain) { domain.exit(); } } flushing = false; } if (typeof process !== "undefined" && process.nextTick) { // Node.js before 0.9. Note that some fake-Node environments, like the // Mocha test runner, introduce a `process` global without a `nextTick`. isNodeJS = true; requestFlush = function () { process.nextTick(flush); }; } else if (typeof setImmediate === "function") { // In IE10, Node.js 0.9+, or https://github.com/NobleJS/setImmediate if (typeof window !== "undefined") { requestFlush = setImmediate.bind(window, flush); } else { requestFlush = function () { setImmediate(flush); }; } } else if (typeof MessageChannel !== "undefined") { // modern browsers // http://www.nonblocking.io/2011/06/windownexttick.html var channel = new MessageChannel(); channel.port1.onmessage = flush; requestFlush = function () { channel.port2.postMessage(0); }; } else { // old browsers requestFlush = function () { setTimeout(flush, 0); }; } function asap(task) { tail = tail.next = { task: task, domain: isNodeJS && process.domain, next: null }; if (!flushing) { flushing = true; requestFlush(); } }; module.exports = asap; }).call(this,require('_process')) },{"_process":2}]},{},[1])(1) });