localforage
Version:
Offline storage, improved.
1,545 lines (1,317 loc) • 61.5 kB
JavaScript
/**
* 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) {