UNPKG

localforage

Version:
1,545 lines (1,317 loc) 61.5 kB
/** * Require the module at `name`. * * @param {String} name * @return {Object} exports * @api public */ function require(name) { var module = require.modules[name]; if (!module) throw new Error('failed to require "' + name + '"'); if (!('exports' in module) && typeof module.definition === 'function') { module.client = module.component = true; module.definition.call(this, module.exports = {}, module); delete module.definition; } return module.exports; } /** * Registered modules. */ require.modules = {}; /** * Register module at `name` with callback `definition`. * * @param {String} name * @param {Function} definition * @api private */ require.register = function (name, definition) { require.modules[name] = { definition: definition }; }; /** * Define a module's exports immediately with `exports`. * * @param {String} name * @param {Generic} exports * @api private */ require.define = function (name, exports) { require.modules[name] = { exports: exports }; }; require.register("johntron~asap@master", function (exports, module) { "use strict"; // 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 hasSetImmediate = typeof setImmediate === "function"; var domain; if (typeof global != 'undefined') { // Avoid shims from browserify. // The existence of `global` in browsers is guaranteed by browserify. var process = global.process; } // Note that some fake-Node environments, // like the Mocha test runner, introduce a `process` global. var isNodeJS = !!process && ({}).toString.call(process) === "[object process]"; function flush() { /* jshint loopfunc: true */ while (head.next) { head = head.next; var task = head.task; head.task = void 0; try { task(); } catch (e) { if (isNodeJS) { // In node, uncaught exceptions are considered fatal errors. // Re-throw them to interrupt flushing! // Ensure continuation if an uncaught exception is suppressed // listening process.on("uncaughtException") or domain("error"). requestFlush(); throw e; } else { // In browsers, uncaught exceptions are not fatal. // Re-throw them asynchronously to avoid slow-downs. setTimeout(function () { throw e; }, 0); } } } flushing = false; } if (isNodeJS) { // Node.js requestFlush = function () { // Ensure flushing is not bound to any domain. var currentDomain = process.domain; if (currentDomain) { domain = domain || (1,require)("domain"); domain.active = process.domain = null; } // Avoid tick recursion - use setImmediate if it exists. if (flushing && hasSetImmediate) { setImmediate(flush); } else { process.nextTick(flush); } if (currentDomain) { domain.active = process.domain = currentDomain; } }; } else if (hasSetImmediate) { // In IE10, or https://github.com/NobleJS/setImmediate requestFlush = function () { setImmediate(flush); }; } else if (typeof MessageChannel !== "undefined") { // modern browsers // http://www.nonblocking.io/2011/06/windownexttick.html var channel = new MessageChannel(); // At least Safari Version 6.0.5 (8536.30.1) intermittently cannot create // working message ports the first time a page loads. channel.port1.onmessage = function () { requestFlush = requestPortFlush; channel.port1.onmessage = flush; flush(); }; var requestPortFlush = function () { // Opera requires us to provide a message payload, regardless of // whether we use it. channel.port2.postMessage(0); }; requestFlush = function () { setTimeout(flush, 0); requestPortFlush(); }; } else { // old browsers requestFlush = function () { setTimeout(flush, 0); }; } function asap(task) { if (isNodeJS && process.domain) { task = process.domain.bind(task); } tail = tail.next = {task: task, next: null}; if (!flushing) { requestFlush(); flushing = true; } }; module.exports = asap; }); require.register("then~promise@4.0.0", function (exports, module) { 'use strict'; //This file contains then/promise specific extensions to the core promise API var Promise = require("then~promise@4.0.0/core.js") var asap = require("johntron~asap@master") 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.from = Promise.cast = 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.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 args = Array.prototype.slice.call(arguments.length === 1 && Array.isArray(arguments[0]) ? arguments[0] : arguments) 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]) } }) } /* 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 (callback === null || typeof callback == 'undefined') 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); } Promise.resolve = function (value) { return new Promise(function (resolve) { resolve(value); }); } Promise.reject = function (value) { return new Promise(function (resolve, reject) { reject(value); }); } Promise.race = function (values) { return new Promise(function (resolve, reject) { values.map(function(value){ Promise.cast(value).then(resolve, reject); }) }); } }); require.register("then~promise@4.0.0/core.js", function (exports, module) { 'use strict'; var asap = require("johntron~asap@master") 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) } } }); require.register("localforage", function (exports, module) { (function() { 'use strict'; // Promises! var Promise = (typeof module !== 'undefined' && module.exports) ? require("then~promise@4.0.0") : this.Promise; // Avoid those magic constants! var MODULE_TYPE_DEFINE = 1; var MODULE_TYPE_EXPORT = 2; var MODULE_TYPE_WINDOW = 3; // Attaching to window (i.e. no module loader) is the assumed, // simple default. var moduleType = MODULE_TYPE_WINDOW; // Find out what kind of module setup we have; if none, we'll just attach // localForage to the main window. if (typeof define === 'function' && define.amd) { moduleType = MODULE_TYPE_DEFINE; } else if (typeof module !== 'undefined' && module.exports) { moduleType = MODULE_TYPE_EXPORT; } // Initialize IndexedDB; fall back to vendor-prefixed versions if needed. var indexedDB = indexedDB || this.indexedDB || this.webkitIndexedDB || this.mozIndexedDB || this.OIndexedDB || this.msIndexedDB; // Check for WebSQL. var openDatabase = this.openDatabase; // 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 _this = this; var localForage = { INDEXEDDB: 'asyncStorage', LOCALSTORAGE: 'localStorageWrapper', WEBSQL: 'webSQLStorage', _config: { description: '', 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 }, // 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. 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) { this._config[i] = options[i]; } return true; } else if (typeof(options) === 'string') { return this._config[options]; } else { return this._config; } }, driver: function() { return this._driver || null; }, _ready: Promise.reject(new Error("setDriver() wasn't called")), setDriver: function(driverName, callback) { var driverSet = new Promise(function(resolve, reject) { if ((!indexedDB && driverName === localForage.INDEXEDDB) || (!openDatabase && driverName === localForage.WEBSQL)) { reject(localForage); return; } localForage._ready = null; // We allow localForage to be declared as a module or as a library // available without AMD/require.js. if (moduleType === MODULE_TYPE_DEFINE) { require([driverName], function(lib) { localForage._extend(lib); resolve(localForage); }); // Return here so we don't resolve the promise twice. return; } else if (moduleType === MODULE_TYPE_EXPORT) { // Making it browserify friendly var driver; switch (driverName) { case localForage.INDEXEDDB: driver = require("localforage/src/drivers/indexeddb.js"); break; case localForage.LOCALSTORAGE: driver = require("localforage/src/drivers/localstorage.js"); break; case localForage.WEBSQL: driver = require("localforage/src/drivers/websql.js"); } localForage._extend(driver); } else { localForage._extend(_this[driverName]); } resolve(localForage); }); driverSet.then(callback, callback); return driverSet; }, ready: function(callback) { if (this._ready === null) { this._ready = this._initStorage(this._config); } this._ready.then(callback, callback); return this._ready; }, _extend: function(libraryMethodsAndProperties) { for (var i in libraryMethodsAndProperties) { if (libraryMethodsAndProperties.hasOwnProperty(i)) { this[i] = libraryMethodsAndProperties[i]; } } } }; // Select our storage library. var storageLibrary; // 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 if (indexedDB && indexedDB.open('_localforage_spec_test', 1).onupgradeneeded === null) { storageLibrary = localForage.INDEXEDDB; } else if (openDatabase) { // WebSQL is available, so we'll use that. storageLibrary = localForage.WEBSQL; } else { // If nothing else is available, we use localStorage. storageLibrary = localForage.LOCALSTORAGE; } // If window.localForageConfig is set, use it for configuration. if (this.localForageConfig) { localForage.config = this.localForageConfig; } // Set the (default) driver. localForage.setDriver(storageLibrary); // We allow localForage to be declared as a module or as a library // available without AMD/require.js. if (moduleType === MODULE_TYPE_DEFINE) { define(function() { return localForage; }); } else if (moduleType === MODULE_TYPE_EXPORT) { module.exports = localForage; } else { this.localforage = localForage; } }).call(this); }); require.register("localforage/src/drivers/indexeddb.js", function (exports, module) { // 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("then~promise@4.0.0") : this.Promise; var db = null; var dbInfo = {}; // 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) { 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 withStoreOnError() { reject(openreq.error); }; openreq.onupgradeneeded = function withStoreOnUpgradeNeeded() { // First time setup: create an empty object store openreq.result.createObjectStore(dbInfo.storeName); }; openreq.onsuccess = function withStoreOnSuccess() { db = openreq.result; resolve(); }; }); } function getItem(key, callback) { var _this = this; return new Promise(function(resolve, reject) { _this.ready().then(function() { var store = 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; } if (callback) { callback(value); } resolve(value); }; req.onerror = function() { if (callback) { callback(null, req.error); } reject(req.error); }; }); }); } function setItem(key, value, callback) { var _this = this; return new Promise(function(resolve, reject) { _this.ready().then(function() { var store = db.transaction(dbInfo.storeName, 'readwrite') .objectStore(dbInfo.storeName); // 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; } var req = store.put(value, key); req.onsuccess = function() { if (callback) { callback(value); } resolve(value); }; req.onerror = function() { if (callback) { callback(null, req.error); } reject(req.error); }; }); }); } function removeItem(key, callback) { var _this = this; return new Promise(function(resolve, reject) { _this.ready().then(function() { var store = db.transaction(dbInfo.storeName, 'readwrite') .objectStore(dbInfo.storeName); // We use `['delete']` instead of `.delete` because IE 8 will // throw a fit if it sees the reserved word "delete" in this // scenario. See: https://github.com/mozilla/localForage/pull/67 // // This can be removed once we no longer care about IE 8, for // what that's worth. // TODO: Write a test against this? Maybe IE in general? Also, // make sure the minify step doesn't optimise this to `.delete`, // though it currently doesn't. var req = store['delete'](key); req.onsuccess = function() { if (callback) { callback(); } resolve(); }; req.onerror = function() { if (callback) { callback(req.error); } 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". req.onabort = function(event) { var error = event.target.error; if (error === 'QuotaExceededError') { if (callback) { callback(error); } reject(error); } }; }); }); } function clear(callback) { var _this = this; return new Promise(function(resolve, reject) { _this.ready().then(function() { var store = db.transaction(dbInfo.storeName, 'readwrite') .objectStore(dbInfo.storeName); var req = store.clear(); req.onsuccess = function() { if (callback) { callback(); } resolve(); }; req.onerror = function() { if (callback) { callback(null, req.error); } reject(req.error); }; }); }); } function length(callback) { var _this = this; return new Promise(function(resolve, reject) { _this.ready().then(function() { var store = db.transaction(dbInfo.storeName, 'readonly') .objectStore(dbInfo.storeName); var req = store.count(); req.onsuccess = function() { if (callback) { callback(req.result); } resolve(req.result); }; req.onerror = function() { if (callback) { callback(null, req.error); } reject(req.error); }; }); }); } function key(n, callback) { var _this = this; return new Promise(function(resolve, reject) { if (n < 0) { if (callback) { callback(null); } resolve(null); return; } _this.ready().then(function() { var store = 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 if (callback) { callback(null); } resolve(null); return; } if (n === 0) { // We have the first key, return it if that's what they // wanted. if (callback) { callback(cursor.key); } 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. if (callback) { callback(cursor.key); } resolve(cursor.key); } } }; req.onerror = function() { if (callback) { callback(null, req.error); } reject(req.error); }; }); }); } var asyncStorage = { _driver: 'asyncStorage', _initStorage: _initStorage, getItem: getItem, setItem: setItem, removeItem: removeItem, clear: clear, length: length, key: key }; if (typeof define === 'function' && define.amd) { define('asyncStorage', function() { return asyncStorage; }); } else if (typeof module !== 'undefined' && module.exports) { module.exports = asyncStorage; } else { this.asyncStorage = asyncStorage; } }).call(this); }); require.register("localforage/src/drivers/localstorage.js", function (exports, module) { // 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'; var keyPrefix = ''; var dbInfo = {}; // Promises! var Promise = (typeof module !== 'undefined' && module.exports) ? require("then~promise@4.0.0") : this.Promise; 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 { // Initialize localStorage and create a variable to use throughout // the code. localStorage = this.localStorage; } catch (e) { return; } // Config the localStorage backend, using options set in the config. function _initStorage(options) { if (options) { for (var i in options) { dbInfo[i] = options[i]; } } keyPrefix = dbInfo.name + '/'; return Promise.resolve(); } 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; // Remove all keys from the datastore, effectively destroying all data in // the app's key/value store! function clear(callback) { var _this = this; return new Promise(function(resolve) { _this.ready().then(function() { localStorage.clear(); if (callback) { callback(); } resolve(); }); }); } // 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 _this = this; return new Promise(function(resolve, reject) { _this.ready().then(function() { try { var result = localStorage.getItem(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 = _deserialize(result); } if (callback) { callback(result, null); } resolve(result); } catch (e) { if (callback) { callback(null, e); } reject(e); } }); }); } // Same as localStorage's key() method, except takes a callback. function key(n, callback) { var _this = this; return new Promise(function(resolve) { _this.ready().then(function() { var result = localStorage.key(n); // Remove the prefix from the key, if a key is found. if (result) { result = result.substring(keyPrefix.length); } if (callback) { callback(result); } resolve(result); }); }); } // Supply the number of keys in the datastore to the callback function. function length(callback) { var _this = this; return new Promise(function(resolve) { _this.ready().then(function() { var result = localStorage.length; if (callback) { callback(result); } resolve(result); }); }); } // Remove an item from the store, nice and simple. function removeItem(key, callback) { var _this = this; return new Promise(function(resolve) { _this.ready().then(function() { localStorage.removeItem(keyPrefix + key); if (callback) { callback(); } resolve(); }); }); } // 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); // Fill the string into a ArrayBuffer. var buffer = new ArrayBuffer(serializedString.length * 2); // 2 bytes for each char var bufferView = new Uint16Array(buffer); for (var i = serializedString.length - 1; i >= 0; i--) { bufferView[i] = serializedString.charCodeAt(i); } // 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); } } // Converts a buffer to a string to store, serialized, in the backend // storage library. function _bufferToString(buffer) { var str = ''; var uint16Array = new Uint16Array(buffer); try { str = String.fromCharCode.apply(null, uint16Array); } catch (e) { // This is a fallback implementation in case the first one does // not work. This is required to get the phantomjs passing... for (var i = 0; i < uint16Array.length; i++) { str += String.fromCharCode(uint16Array[i]); } } return str; } // 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) { if (this.console && this.console.error) { this.console.error("Couldn't convert value into a JSON string: ", value); } callback(null, e); } } } // 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 _this = this; return new Promise(function(resolve, reject) { _this.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; _serialize(value, function(value, error) { if (error) { if (callback) { callback(null, error); } reject(error); } else { try { localStorage.setItem(keyPrefix + key, value); } catch (e) { // localStorage capacity exceeded. // TODO: Make this a specific error/event. if (e.name === 'QuotaExceededError' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED') { if (callback) { callback(null, e); } reject(e); } } if (callback) { callback(originalValue); } resolve(originalValue); } }); }); }); } var localStorageWrapper = { _driver: 'localStorageWrapper', _initStorage: _initStorage, // Default API, from Gaia/localStorage. getItem: getItem, setItem: setItem, removeItem: removeItem, clear: clear, length: length, key: key }; if (typeof define === 'function' && define.amd) { define('localStorageWrapper', function() { return localStorageWrapper; }); } else if (typeof module !== 'undefined' && module.exports) { module.exports = localStorageWrapper; } else { this.localStorageWrapper = localStorageWrapper; } }).call(this); }); require.register("localforage/src/drivers/websql.js", function (exports, module) { /* * 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'; // Sadly, the best way to save binary data in WebSQL is Base64 serializing // it, 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+/'; // Promises! var Promise = (typeof module !== 'undefined' && module.exports) ? require("then~promise@4.0.0") : this.Promise; var openDatabase = this.openDatabase; var db = null; var dbInfo = {}; 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; // If WebSQL methods aren't available, we can stop now. if (!openDatabase) { return; } // Open the WebSQL database (automatically creates one if one didn't // previously exist), using any options set in the config. function _initStorage(options) { var _this = this; if (options) { for (var i in dbInfo) { dbInfo[i] = typeof(options[i]) !== 'string' ? options[i].toString() : options[i]; } } return new Promise(function(resolve) { // Open the database; the openDatabase API will automatically // create it for us if it doesn't exist. try { db = openDatabase(dbInfo.name, dbInfo.version, dbInfo.description, dbInfo.size); } catch (e) { return _this.setDriver('localStorageWrapper').then(resolve); } // Create our key/value table if it doesn't exist. db.transaction(function (t) { t.executeSql('CREATE TABLE IF NOT EXISTS ' + dbInfo.storeName + ' (id INTEGER PRIMARY KEY, key unique, value)', [], function() { resolve(); }, null); }); }); } function getItem(key, callback) { var _this = this; return new Promise(function(resolve, reject) { _this.ready().then(function() { 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 = _deserialize(result); } if (callback) { callback(result); } resolve(result); }, function(t, error) { if (callback) { callback(null, error); } reject(error); }); }); }); }); } function setItem(key, value, callback) { var _this = this; return new Promise(function(resolve, reject) { _this.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; _serialize(value, function(value, error) { if (error) { reject(error); } else { db.transaction(function (t) { t.executeSql('INSERT OR REPLACE INTO ' + dbInfo.storeName + ' (key, value) VALUES (?, ?)', [key, value], function() { if (callback) { callback(originalValue); } resolve(originalValue); }, function(t, error) { if (callback) { callback(null, 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. if (callback) { callback(null, sqlError); } reject(sqlError); } }); } }); }); }); } function removeItem(key, callback) { var _this = this; return new Promise(function(resolve, reject) { _this.ready().then(function() { db.transaction(function (t) { t.executeSql('DELETE FROM ' + dbInfo.storeName + ' WHERE key = ?', [key], function() { if (callback) { callback(); } resolve(); }, function(t, error) {