summaryrefslogtreecommitdiff
path: root/public/editor/localforage
diff options
context:
space:
mode:
authorAaron Parecki <aaron@parecki.com>2015-05-10 14:05:06 +0200
committerAaron Parecki <aaron@parecki.com>2015-05-10 14:05:06 +0200
commit4898ed74a57b6d14cbcd962e967111adffd4595d (patch)
tree5eec23250a6eff45d6b694b880120f84a3ebb073 /public/editor/localforage
parentfb6d4181e429833f083ddd7e1770ed99a70949db (diff)
set up medium-style editor with image embedding and appcache
Diffstat (limited to 'public/editor/localforage')
-rw-r--r--public/editor/localforage/localforage.js2497
1 files changed, 2497 insertions, 0 deletions
diff --git a/public/editor/localforage/localforage.js b/public/editor/localforage/localforage.js
new file mode 100644
index 0000000..42e5391
--- /dev/null
+++ b/public/editor/localforage/localforage.js
@@ -0,0 +1,2497 @@
+/*!
+ localForage -- Offline Storage, Improved
+ Version 1.2.2
+ https://mozilla.github.io/localForage
+ (c) 2013-2015 Mozilla, Apache License 2.0
+*/
+(function() {
+var define, requireModule, require, requirejs;
+
+(function() {
+ var registry = {}, seen = {};
+
+ define = function(name, deps, callback) {
+ registry[name] = { deps: deps, callback: callback };
+ };
+
+ requirejs = require = requireModule = function(name) {
+ requirejs._eak_seen = registry;
+
+ if (seen[name]) { return seen[name]; }
+ seen[name] = {};
+
+ if (!registry[name]) {
+ throw new Error("Could not find module " + name);
+ }
+
+ var mod = registry[name],
+ deps = mod.deps,
+ callback = mod.callback,
+ reified = [],
+ exports;
+
+ for (var i=0, l=deps.length; i<l; i++) {
+ if (deps[i] === 'exports') {
+ reified.push(exports = {});
+ } else {
+ reified.push(requireModule(resolve(deps[i])));
+ }
+ }
+
+ var value = callback.apply(this, reified);
+ return seen[name] = exports || value;
+
+ function resolve(child) {
+ if (child.charAt(0) !== '.') { return child; }
+ var parts = child.split("/");
+ var parentBase = name.split("/").slice(0, -1);
+
+ for (var i=0, l=parts.length; i<l; i++) {
+ var part = parts[i];
+
+ if (part === '..') { parentBase.pop(); }
+ else if (part === '.') { continue; }
+ else { parentBase.push(part); }
+ }
+
+ return parentBase.join("/");
+ }
+ };
+})();
+
+define("promise/all",
+ ["./utils","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ /* global toString */
+
+ var isArray = __dependency1__.isArray;
+ var isFunction = __dependency1__.isFunction;
+
+ /**
+ Returns a promise that is fulfilled when all the given promises have been
+ fulfilled, or rejected if any of them become rejected. The return promise
+ is fulfilled with an array that gives all the values in the order they were
+ passed in the `promises` array argument.
+
+ Example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.resolve(2);
+ var promise3 = RSVP.resolve(3);
+ var promises = [ promise1, promise2, promise3 ];
+
+ RSVP.all(promises).then(function(array){
+ // The array here would be [ 1, 2, 3 ];
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.all` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promises's
+ rejection handler. For example:
+
+ Example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.reject(new Error("2"));
+ var promise3 = RSVP.reject(new Error("3"));
+ var promises = [ promise1, promise2, promise3 ];
+
+ RSVP.all(promises).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(error) {
+ // error.message === "2"
+ });
+ ```
+
+ @method all
+ @for RSVP
+ @param {Array} promises
+ @param {String} label
+ @return {Promise} promise that is fulfilled when all `promises` have been
+ fulfilled, or rejected if any of them become rejected.
+ */
+ function all(promises) {
+ /*jshint validthis:true */
+ var Promise = this;
+
+ if (!isArray(promises)) {
+ throw new TypeError('You must pass an array to all.');
+ }
+
+ return new Promise(function(resolve, reject) {
+ var results = [], remaining = promises.length,
+ promise;
+
+ if (remaining === 0) {
+ resolve([]);
+ }
+
+ function resolver(index) {
+ return function(value) {
+ resolveAll(index, value);
+ };
+ }
+
+ function resolveAll(index, value) {
+ results[index] = value;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ }
+
+ for (var i = 0; i < promises.length; i++) {
+ promise = promises[i];
+
+ if (promise && isFunction(promise.then)) {
+ promise.then(resolver(i), reject);
+ } else {
+ resolveAll(i, promise);
+ }
+ }
+ });
+ }
+
+ __exports__.all = all;
+ });
+define("promise/asap",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ var browserGlobal = (typeof window !== 'undefined') ? window : {};
+ var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+ var local = (typeof global !== 'undefined') ? global : (this === undefined? window:this);
+
+ // node
+ function useNextTick() {
+ return function() {
+ process.nextTick(flush);
+ };
+ }
+
+ function useMutationObserver() {
+ var iterations = 0;
+ var observer = new BrowserMutationObserver(flush);
+ var node = document.createTextNode('');
+ observer.observe(node, { characterData: true });
+
+ return function() {
+ node.data = (iterations = ++iterations % 2);
+ };
+ }
+
+ function useSetTimeout() {
+ return function() {
+ local.setTimeout(flush, 1);
+ };
+ }
+
+ var queue = [];
+ function flush() {
+ for (var i = 0; i < queue.length; i++) {
+ var tuple = queue[i];
+ var callback = tuple[0], arg = tuple[1];
+ callback(arg);
+ }
+ queue = [];
+ }
+
+ var scheduleFlush;
+
+ // Decide what async method to use to triggering processing of queued callbacks:
+ if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+ scheduleFlush = useNextTick();
+ } else if (BrowserMutationObserver) {
+ scheduleFlush = useMutationObserver();
+ } else {
+ scheduleFlush = useSetTimeout();
+ }
+
+ function asap(callback, arg) {
+ var length = queue.push([callback, arg]);
+ if (length === 1) {
+ // If length is 1, that means that we need to schedule an async flush.
+ // If additional callbacks are queued before the queue is flushed, they
+ // will be processed by this flush that we are scheduling.
+ scheduleFlush();
+ }
+ }
+
+ __exports__.asap = asap;
+ });
+define("promise/config",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ var config = {
+ instrument: false
+ };
+
+ function configure(name, value) {
+ if (arguments.length === 2) {
+ config[name] = value;
+ } else {
+ return config[name];
+ }
+ }
+
+ __exports__.config = config;
+ __exports__.configure = configure;
+ });
+define("promise/polyfill",
+ ["./promise","./utils","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ /*global self*/
+ var RSVPPromise = __dependency1__.Promise;
+ var isFunction = __dependency2__.isFunction;
+
+ function polyfill() {
+ var local;
+
+ if (typeof global !== 'undefined') {
+ local = global;
+ } else if (typeof window !== 'undefined' && window.document) {
+ local = window;
+ } else {
+ local = self;
+ }
+
+ var es6PromiseSupport =
+ "Promise" in local &&
+ // Some of these methods are missing from
+ // Firefox/Chrome experimental implementations
+ "resolve" in local.Promise &&
+ "reject" in local.Promise &&
+ "all" in local.Promise &&
+ "race" in local.Promise &&
+ // Older version of the spec had a resolver object
+ // as the arg rather than a function
+ (function() {
+ var resolve;
+ new local.Promise(function(r) { resolve = r; });
+ return isFunction(resolve);
+ }());
+
+ if (!es6PromiseSupport) {
+ local.Promise = RSVPPromise;
+ }
+ }
+
+ __exports__.polyfill = polyfill;
+ });
+define("promise/promise",
+ ["./config","./utils","./all","./race","./resolve","./reject","./asap","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
+ "use strict";
+ var config = __dependency1__.config;
+ var configure = __dependency1__.configure;
+ var objectOrFunction = __dependency2__.objectOrFunction;
+ var isFunction = __dependency2__.isFunction;
+ var now = __dependency2__.now;
+ var all = __dependency3__.all;
+ var race = __dependency4__.race;
+ var staticResolve = __dependency5__.resolve;
+ var staticReject = __dependency6__.reject;
+ var asap = __dependency7__.asap;
+
+ var counter = 0;
+
+ config.async = asap; // default async is asap;
+
+ function Promise(resolver) {
+ if (!isFunction(resolver)) {
+ throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+ }
+
+ if (!(this instanceof Promise)) {
+ throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+ }
+
+ this._subscribers = [];
+
+ invokeResolver(resolver, this);
+ }
+
+ function invokeResolver(resolver, promise) {
+ function resolvePromise(value) {
+ resolve(promise, value);
+ }
+
+ function rejectPromise(reason) {
+ reject(promise, reason);
+ }
+
+ try {
+ resolver(resolvePromise, rejectPromise);
+ } catch(e) {
+ rejectPromise(e);
+ }
+ }
+
+ function invokeCallback(settled, promise, callback, detail) {
+ var hasCallback = isFunction(callback),
+ value, error, succeeded, failed;
+
+ if (hasCallback) {
+ try {
+ value = callback(detail);
+ succeeded = true;
+ } catch(e) {
+ failed = true;
+ error = e;
+ }
+ } else {
+ value = detail;
+ succeeded = true;
+ }
+
+ if (handleThenable(promise, value)) {
+ return;
+ } else if (hasCallback && succeeded) {
+ resolve(promise, value);
+ } else if (failed) {
+ reject(promise, error);
+ } else if (settled === FULFILLED) {
+ resolve(promise, value);
+ } else if (settled === REJECTED) {
+ reject(promise, value);
+ }
+ }
+
+ var PENDING = void 0;
+ var SEALED = 0;
+ var FULFILLED = 1;
+ var REJECTED = 2;
+
+ function subscribe(parent, child, onFulfillment, onRejection) {
+ var subscribers = parent._subscribers;
+ var length = subscribers.length;
+
+ subscribers[length] = child;
+ subscribers[length + FULFILLED] = onFulfillment;
+ subscribers[length + REJECTED] = onRejection;
+ }
+
+ function publish(promise, settled) {
+ var child, callback, subscribers = promise._subscribers, detail = promise._detail;
+
+ for (var i = 0; i < subscribers.length; i += 3) {
+ child = subscribers[i];
+ callback = subscribers[i + settled];
+
+ invokeCallback(settled, child, callback, detail);
+ }
+
+ promise._subscribers = null;
+ }
+
+ Promise.prototype = {
+ constructor: Promise,
+
+ _state: undefined,
+ _detail: undefined,
+ _subscribers: undefined,
+
+ then: function(onFulfillment, onRejection) {
+ var promise = this;
+
+ var thenPromise = new this.constructor(function() {});
+
+ if (this._state) {
+ var callbacks = arguments;
+ config.async(function invokePromiseCallback() {
+ invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);
+ });
+ } else {
+ subscribe(this, thenPromise, onFulfillment, onRejection);
+ }
+
+ return thenPromise;
+ },
+
+ 'catch': function(onRejection) {
+ return this.then(null, onRejection);
+ }
+ };
+
+ Promise.all = all;
+ Promise.race = race;
+ Promise.resolve = staticResolve;
+ Promise.reject = staticReject;
+
+ function handleThenable(promise, value) {
+ var then = null,
+ resolved;
+
+ try {
+ if (promise === value) {
+ throw new TypeError("A promises callback cannot return that same promise.");
+ }
+
+ if (objectOrFunction(value)) {
+ then = value.then;
+
+ if (isFunction(then)) {
+ then.call(value, function(val) {
+ if (resolved) { return true; }
+ resolved = true;
+
+ if (value !== val) {
+ resolve(promise, val);
+ } else {
+ fulfill(promise, val);
+ }
+ }, function(val) {
+ if (resolved) { return true; }
+ resolved = true;
+
+ reject(promise, val);
+ });
+
+ return true;
+ }
+ }
+ } catch (error) {
+ if (resolved) { return true; }
+ reject(promise, error);
+ return true;
+ }
+
+ return false;
+ }
+
+ function resolve(promise, value) {
+ if (promise === value) {
+ fulfill(promise, value);
+ } else if (!handleThenable(promise, value)) {
+ fulfill(promise, value);
+ }
+ }
+
+ function fulfill(promise, value) {
+ if (promise._state !== PENDING) { return; }
+ promise._state = SEALED;
+ promise._detail = value;
+
+ config.async(publishFulfillment, promise);
+ }
+
+ function reject(promise, reason) {
+ if (promise._state !== PENDING) { return; }
+ promise._state = SEALED;
+ promise._detail = reason;
+
+ config.async(publishRejection, promise);
+ }
+
+ function publishFulfillment(promise) {
+ publish(promise, promise._state = FULFILLED);
+ }
+
+ function publishRejection(promise) {
+ publish(promise, promise._state = REJECTED);
+ }
+
+ __exports__.Promise = Promise;
+ });
+define("promise/race",
+ ["./utils","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ /* global toString */
+ var isArray = __dependency1__.isArray;
+
+ /**
+ `RSVP.race` allows you to watch a series of promises and act as soon as the
+ first promise given to the `promises` argument fulfills or rejects.
+
+ Example:
+
+ ```javascript
+ var promise1 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 1");
+ }, 200);
+ });
+
+ var promise2 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 2");
+ }, 100);
+ });
+
+ RSVP.race([promise1, promise2]).then(function(result){
+ // result === "promise 2" because it was resolved before promise1
+ // was resolved.
+ });
+ ```
+
+ `RSVP.race` is deterministic in that only the state of the first completed
+ promise matters. For example, even if other promises given to the `promises`
+ array argument are resolved, but the first completed promise has become
+ rejected before the other promises became fulfilled, the returned promise
+ will become rejected:
+
+ ```javascript
+ var promise1 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 1");
+ }, 200);
+ });
+
+ var promise2 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ reject(new Error("promise 2"));
+ }, 100);
+ });
+
+ RSVP.race([promise1, promise2]).then(function(result){
+ // Code here never runs because there are rejected promises!
+ }, function(reason){
+ // reason.message === "promise2" because promise 2 became rejected before
+ // promise 1 became fulfilled
+ });
+ ```
+
+ @method race
+ @for RSVP
+ @param {Array} promises array of promises to observe
+ @param {String} label optional string for describing the promise returned.
+ Useful for tooling.
+ @return {Promise} a promise that becomes fulfilled with the value the first
+ completed promises is resolved with if the first completed promise was
+ fulfilled, or rejected with the reason that the first completed promise
+ was rejected with.
+ */
+ function race(promises) {
+ /*jshint validthis:true */
+ var Promise = this;
+
+ if (!isArray(promises)) {
+ throw new TypeError('You must pass an array to race.');
+ }
+ return new Promise(function(resolve, reject) {
+ var results = [], promise;
+
+ for (var i = 0; i < promises.length; i++) {
+ promise = promises[i];
+
+ if (promise && typeof promise.then === 'function') {
+ promise.then(resolve, reject);
+ } else {
+ resolve(promise);
+ }
+ }
+ });
+ }
+
+ __exports__.race = race;
+ });
+define("promise/reject",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.reject` returns a promise that will become rejected with the passed
+ `reason`. `RSVP.reject` is essentially shorthand for the following:
+
+ ```javascript
+ var promise = new RSVP.Promise(function(resolve, reject){
+ reject(new Error('WHOOPS'));
+ });
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ Instead of writing the above, your code now simply becomes the following:
+
+ ```javascript
+ var promise = RSVP.reject(new Error('WHOOPS'));
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ @method reject
+ @for RSVP
+ @param {Any} reason value that the returned promise will be rejected with.
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise that will become rejected with the given
+ `reason`.
+ */
+ function reject(reason) {
+ /*jshint validthis:true */
+ var Promise = this;
+
+ return new Promise(function (resolve, reject) {
+ reject(reason);
+ });
+ }
+
+ __exports__.reject = reject;
+ });
+define("promise/resolve",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function resolve(value) {
+ /*jshint validthis:true */
+ if (value && typeof value === 'object' && value.constructor === this) {
+ return value;
+ }
+
+ var Promise = this;
+
+ return new Promise(function(resolve) {
+ resolve(value);
+ });
+ }
+
+ __exports__.resolve = resolve;
+ });
+define("promise/utils",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function objectOrFunction(x) {
+ return isFunction(x) || (typeof x === "object" && x !== null);
+ }
+
+ function isFunction(x) {
+ return typeof x === "function";
+ }
+
+ function isArray(x) {
+ return Object.prototype.toString.call(x) === "[object Array]";
+ }
+
+ // Date.now is not available in browsers < IE9
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
+ var now = Date.now || function() { return new Date().getTime(); };
+
+
+ __exports__.objectOrFunction = objectOrFunction;
+ __exports__.isFunction = isFunction;
+ __exports__.isArray = isArray;
+ __exports__.now = now;
+ });
+requireModule('promise/polyfill').polyfill();
+}());(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);
+// Some code originally from async_storage.js in
+// [Gaia](https://github.com/mozilla-b2g/gaia).
+(function() {
+ 'use strict';
+
+ // Originally found in https://github.com/mozilla-b2g/gaia/blob/e8f624e4cc9ea945727278039b3bc9bcb9f8667a/shared/js/async_storage.js
+
+ // Promises!
+ var Promise = (typeof module !== 'undefined' && module.exports) ?
+ require('promise') : this.Promise;
+
+ // Initialize IndexedDB; fall back to vendor-prefixed versions if needed.
+ var indexedDB = indexedDB || this.indexedDB || this.webkitIndexedDB ||
+ this.mozIndexedDB || this.OIndexedDB ||
+ this.msIndexedDB;
+
+ // If IndexedDB isn't available, we get outta here!
+ if (!indexedDB) {
+ return;
+ }
+
+ // Open the IndexedDB database (automatically creates one if one didn't
+ // previously exist), using any options set in the config.
+ function _initStorage(options) {
+ var self = this;
+ var dbInfo = {
+ db: null
+ };
+
+ if (options) {
+ for (var i in options) {
+ dbInfo[i] = options[i];
+ }
+ }
+
+ return new Promise(function(resolve, reject) {
+ var openreq = indexedDB.open(dbInfo.name, dbInfo.version);
+ openreq.onerror = function() {
+ reject(openreq.error);
+ };
+ openreq.onupgradeneeded = function() {
+ // First time setup: create an empty object store
+ openreq.result.createObjectStore(dbInfo.storeName);
+ };
+ openreq.onsuccess = function() {
+ dbInfo.db = openreq.result;
+ self._dbInfo = dbInfo;
+ resolve();
+ };
+ });
+ }
+
+ function getItem(key, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ window.console.warn(key +
+ ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = new Promise(function(resolve, reject) {
+ self.ready().then(function() {
+ var dbInfo = self._dbInfo;
+ var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
+ .objectStore(dbInfo.storeName);
+ var req = store.get(key);
+
+ req.onsuccess = function() {
+ var value = req.result;
+ if (value === undefined) {
+ value = null;
+ }
+
+ resolve(value);
+ };
+
+ req.onerror = function() {
+ reject(req.error);
+ };
+ })["catch"](reject);
+ });
+
+ executeDeferedCallback(promise, callback);
+ return promise;
+ }
+
+ // Iterate over all items stored in database.
+ function iterate(iterator, callback) {
+ var self = this;
+
+ var promise = new Promise(function(resolve, reject) {
+ self.ready().then(function() {
+ var dbInfo = self._dbInfo;
+ var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
+ .objectStore(dbInfo.storeName);
+
+ var req = store.openCursor();
+ var iterationNumber = 1;
+
+ req.onsuccess = function() {
+ var cursor = req.result;
+
+ if (cursor) {
+ var result = iterator(cursor.value, cursor.key, iterationNumber++);
+
+ if (result !== void(0)) {
+ resolve(result);
+ } else {
+ cursor["continue"]();
+ }
+ } else {
+ resolve();
+ }
+ };
+
+ req.onerror = function() {
+ reject(req.error);
+ };
+ })["catch"](reject);
+ });
+
+ executeDeferedCallback(promise, callback);
+
+ return promise;
+ }
+
+ function setItem(key, value, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ window.console.warn(key +
+ ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = new Promise(function(resolve, reject) {
+ self.ready().then(function() {
+ var dbInfo = self._dbInfo;
+ var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
+ var store = transaction.objectStore(dbInfo.storeName);
+
+ // The reason we don't _save_ null is because IE 10 does
+ // not support saving the `null` type in IndexedDB. How
+ // ironic, given the bug below!
+ // See: https://github.com/mozilla/localForage/issues/161
+ if (value === null) {
+ value = undefined;
+ }
+
+ var req = store.put(value, key);
+ transaction.oncomplete = function() {
+ // Cast to undefined so the value passed to
+ // callback/promise is the same as what one would get out
+ // of `getItem()` later. This leads to some weirdness
+ // (setItem('foo', undefined) will return `null`), but
+ // it's not my fault localStorage is our baseline and that
+ // it's weird.
+ if (value === undefined) {
+ value = null;
+ }
+
+ resolve(value);
+ };
+ transaction.onabort = transaction.onerror = function() {
+ reject(req.error);
+ };
+ })["catch"](reject);
+ });
+
+ executeDeferedCallback(promise, callback);
+ return promise;
+ }
+
+ function removeItem(key, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ window.console.warn(key +
+ ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = new Promise(function(resolve, reject) {
+ self.ready().then(function() {
+ var dbInfo = self._dbInfo;
+ var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
+ var store = transaction.objectStore(dbInfo.storeName);
+
+ // We use a Grunt task to make this safe for IE and some
+ // versions of Android (including those used by Cordova).
+ // Normally IE won't like `.delete()` and will insist on
+ // using `['delete']()`, but we have a build step that
+ // fixes this for us now.
+ var req = store["delete"](key);
+ transaction.oncomplete = function() {
+ resolve();
+ };
+
+ transaction.onerror = function() {
+ reject(req.error);
+ };
+
+ // The request will be aborted if we've exceeded our storage
+ // space. In this case, we will reject with a specific
+ // "QuotaExceededError".
+ transaction.onabort = function(event) {
+ var error = event.target.error;
+ if (error === 'QuotaExceededError') {
+ reject(error);
+ }
+ };
+ })["catch"](reject);
+ });
+
+ executeDeferedCallback(promise, callback);
+ return promise;
+ }
+
+ function clear(callback) {
+ var self = this;
+
+ var promise = new Promise(function(resolve, reject) {
+ self.ready().then(function() {
+ var dbInfo = self._dbInfo;
+ var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
+ var store = transaction.objectStore(dbInfo.storeName);
+ var req = store.clear();
+
+ transaction.oncomplete = function() {
+ resolve();
+ };
+
+ transaction.onabort = transaction.onerror = function() {
+ reject(req.error);
+ };
+ })["catch"](reject);
+ });
+
+ executeDeferedCallback(promise, callback);
+ return promise;
+ }
+
+ function length(callback) {
+ var self = this;
+
+ var promise = new Promise(function(resolve, reject) {
+ self.ready().then(function() {
+ var dbInfo = self._dbInfo;
+ var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
+ .objectStore(dbInfo.storeName);
+ var req = store.count();
+
+ req.onsuccess = function() {
+ resolve(req.result);
+ };
+
+ req.onerror = function() {
+ reject(req.error);
+ };
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+ }
+
+ function key(n, callback) {
+ var self = this;
+
+ var promise = new Promise(function(resolve, reject) {
+ if (n < 0) {
+ resolve(null);
+
+ return;
+ }
+
+ self.ready().then(function() {
+ var dbInfo = self._dbInfo;
+ var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
+ .objectStore(dbInfo.storeName);
+
+ var advanced = false;
+ var req = store.openCursor();
+ req.onsuccess = function() {
+ var cursor = req.result;
+ if (!cursor) {
+ // this means there weren't enough keys
+ resolve(null);
+
+ return;
+ }
+
+ if (n === 0) {
+ // We have the first key, return it if that's what they
+ // wanted.
+ resolve(cursor.key);
+ } else {
+ if (!advanced) {
+ // Otherwise, ask the cursor to skip ahead n
+ // records.
+ advanced = true;
+ cursor.advance(n);
+ } else {
+ // When we get here, we've got the nth key.
+ resolve(cursor.key);
+ }
+ }
+ };
+
+ req.onerror = function() {
+ reject(req.error);
+ };
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+ }
+
+ function keys(callback) {
+ var self = this;
+
+ var promise = new Promise(function(resolve, reject) {
+ self.ready().then(function() {
+ var dbInfo = self._dbInfo;
+ var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
+ .objectStore(dbInfo.storeName);
+
+ var req = store.openCursor();
+ var keys = [];
+
+ req.onsuccess = function() {
+ var cursor = req.result;
+
+ if (!cursor) {
+ resolve(keys);
+ return;
+ }
+
+ keys.push(cursor.key);
+ cursor["continue"]();
+ };
+
+ req.onerror = function() {
+ reject(req.error);
+ };
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+ }
+
+ function executeCallback(promise, callback) {
+ if (callback) {
+ promise.then(function(result) {
+ callback(null, result);
+ }, function(error) {
+ callback(error);
+ });
+ }
+ }
+
+ function executeDeferedCallback(promise, callback) {
+ if (callback) {
+ promise.then(function(result) {
+ deferCallback(callback, result);
+ }, function(error) {
+ callback(error);
+ });
+ }
+ }
+
+ // Under Chrome the callback is called before the changes (save, clear)
+ // are actually made. So we use a defer function which wait that the
+ // call stack to be empty.
+ // For more info : https://github.com/mozilla/localForage/issues/175
+ // Pull request : https://github.com/mozilla/localForage/pull/178
+ function deferCallback(callback, result) {
+ if (callback) {
+ return setTimeout(function() {
+ return callback(null, result);
+ }, 0);
+ }
+ }
+
+ var asyncStorage = {
+ _driver: 'asyncStorage',
+ _initStorage: _initStorage,
+ iterate: iterate,
+ getItem: getItem,
+ setItem: setItem,
+ removeItem: removeItem,
+ clear: clear,
+ length: length,
+ key: key,
+ keys: keys
+ };
+
+ if (typeof module !== 'undefined' && module.exports) {
+ module.exports = asyncStorage;
+ } else if (typeof define === 'function' && define.amd) {
+ define('asyncStorage', function() {
+ return asyncStorage;
+ });
+ } else {
+ this.asyncStorage = asyncStorage;
+ }
+}).call(window);
+// If IndexedDB isn't available, we'll fall back to localStorage.
+// Note that this will have considerable performance and storage
+// side-effects (all data will be serialized on save and only data that
+// can be converted to a string via `JSON.stringify()` will be saved).
+(function() {
+ 'use strict';
+
+ // Promises!
+ var Promise = (typeof module !== 'undefined' && module.exports) ?
+ require('promise') : this.Promise;
+
+ var globalObject = this;
+ var serializer = null;
+ var localStorage = null;
+
+ // If the app is running inside a Google Chrome packaged webapp, or some
+ // other context where localStorage isn't available, we don't use
+ // localStorage. This feature detection is preferred over the old
+ // `if (window.chrome && window.chrome.runtime)` code.
+ // See: https://github.com/mozilla/localForage/issues/68
+ try {
+ // If localStorage isn't available, we get outta here!
+ // This should be inside a try catch
+ if (!this.localStorage || !('setItem' in this.localStorage)) {
+ return;
+ }
+ // Initialize localStorage and create a variable to use throughout
+ // the code.
+ localStorage = this.localStorage;
+ } catch (e) {
+ return;
+ }
+
+ var ModuleType = {
+ DEFINE: 1,
+ EXPORT: 2,
+ WINDOW: 3
+ };
+
+ // Attaching to window (i.e. no module loader) is the assumed,
+ // simple default.
+ var moduleType = ModuleType.WINDOW;
+
+ // Find out what kind of module setup we have; if none, we'll just attach
+ // localForage to the main window.
+ if (typeof module !== 'undefined' && module.exports) {
+ moduleType = ModuleType.EXPORT;
+ } else if (typeof define === 'function' && define.amd) {
+ moduleType = ModuleType.DEFINE;
+ }
+
+ // Config the localStorage backend, using options set in the config.
+ function _initStorage(options) {
+ var self = this;
+ var dbInfo = {};
+ if (options) {
+ for (var i in options) {
+ dbInfo[i] = options[i];
+ }
+ }
+
+ dbInfo.keyPrefix = dbInfo.name + '/';
+
+ self._dbInfo = dbInfo;
+
+ var serializerPromise = new Promise(function(resolve/*, reject*/) {
+ // We allow localForage to be declared as a module or as a
+ // library available without AMD/require.js.
+ if (moduleType === ModuleType.DEFINE) {
+ require(['localforageSerializer'], resolve);
+ } else if (moduleType === ModuleType.EXPORT) {
+ // Making it browserify friendly
+ resolve(require('./../utils/serializer'));
+ } else {
+ resolve(globalObject.localforageSerializer);
+ }
+ });
+
+ return serializerPromise.then(function(lib) {
+ serializer = lib;
+ return Promise.resolve();
+ });
+ }
+
+ // Remove all keys from the datastore, effectively destroying all data in
+ // the app's key/value store!
+ function clear(callback) {
+ var self = this;
+ var promise = self.ready().then(function() {
+ var keyPrefix = self._dbInfo.keyPrefix;
+
+ for (var i = localStorage.length - 1; i >= 0; i--) {
+ var key = localStorage.key(i);
+
+ if (key.indexOf(keyPrefix) === 0) {
+ localStorage.removeItem(key);
+ }
+ }
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+ }
+
+ // Retrieve an item from the store. Unlike the original async_storage
+ // library in Gaia, we don't modify return values at all. If a key's value
+ // is `undefined`, we pass that value to the callback function.
+ function getItem(key, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ window.console.warn(key +
+ ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = self.ready().then(function() {
+ var dbInfo = self._dbInfo;
+ var result = localStorage.getItem(dbInfo.keyPrefix + key);
+
+ // If a result was found, parse it from the serialized
+ // string into a JS object. If result isn't truthy, the key
+ // is likely undefined and we'll pass it straight to the
+ // callback.
+ if (result) {
+ result = serializer.deserialize(result);
+ }
+
+ return result;
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+ }
+
+ // Iterate over all items in the store.
+ function iterate(iterator, callback) {
+ var self = this;
+
+ var promise = self.ready().then(function() {
+ var keyPrefix = self._dbInfo.keyPrefix;
+ var keyPrefixLength = keyPrefix.length;
+ var length = localStorage.length;
+
+ for (var i = 0; i < length; i++) {
+ var key = localStorage.key(i);
+ var value = localStorage.getItem(key);
+
+ // If a result was found, parse it from the serialized
+ // string into a JS object. If result isn't truthy, the
+ // key is likely undefined and we'll pass it straight
+ // to the iterator.
+ if (value) {
+ value = serializer.deserialize(value);
+ }
+
+ value = iterator(value, key.substring(keyPrefixLength), i + 1);
+
+ if (value !== void(0)) {
+ return value;
+ }
+ }
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+ }
+
+ // Same as localStorage's key() method, except takes a callback.
+ function key(n, callback) {
+ var self = this;
+ var promise = self.ready().then(function() {
+ var dbInfo = self._dbInfo;
+ var result;
+ try {
+ result = localStorage.key(n);
+ } catch (error) {
+ result = null;
+ }
+
+ // Remove the prefix from the key, if a key is found.
+ if (result) {
+ result = result.substring(dbInfo.keyPrefix.length);
+ }
+
+ return result;
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+ }
+
+ function keys(callback) {
+ var self = this;
+ var promise = self.ready().then(function() {
+ var dbInfo = self._dbInfo;
+ var length = localStorage.length;
+ var keys = [];
+
+ for (var i = 0; i < length; i++) {
+ if (localStorage.key(i).indexOf(dbInfo.keyPrefix) === 0) {
+ keys.push(localStorage.key(i).substring(dbInfo.keyPrefix.length));
+ }
+ }
+
+ return keys;
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+ }
+
+ // Supply the number of keys in the datastore to the callback function.
+ function length(callback) {
+ var self = this;
+ var promise = self.keys().then(function(keys) {
+ return keys.length;
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+ }
+
+ // Remove an item from the store, nice and simple.
+ function removeItem(key, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ window.console.warn(key +
+ ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = self.ready().then(function() {
+ var dbInfo = self._dbInfo;
+ localStorage.removeItem(dbInfo.keyPrefix + key);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+ }
+
+ // Set a key's value and run an optional callback once the value is set.
+ // Unlike Gaia's implementation, the callback function is passed the value,
+ // in case you want to operate on that value only after you're sure it
+ // saved, or something like that.
+ function setItem(key, value, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ window.console.warn(key +
+ ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = self.ready().then(function() {
+ // Convert undefined values to null.
+ // https://github.com/mozilla/localForage/pull/42
+ if (value === undefined) {
+ value = null;
+ }
+
+ // Save the original value to pass to the callback.
+ var originalValue = value;
+
+ return new Promise(function(resolve, reject) {
+ serializer.serialize(value, function(value, error) {
+ if (error) {
+ reject(error);
+ } else {
+ try {
+ var dbInfo = self._dbInfo;
+ localStorage.setItem(dbInfo.keyPrefix + key, value);
+ resolve(originalValue);
+ } catch (e) {
+ // localStorage capacity exceeded.
+ // TODO: Make this a specific error/event.
+ if (e.name === 'QuotaExceededError' ||
+ e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
+ reject(e);
+ }
+ reject(e);
+ }
+ }
+ });
+ });
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+ }
+
+ function executeCallback(promise, callback) {
+ if (callback) {
+ promise.then(function(result) {
+ callback(null, result);
+ }, function(error) {
+ callback(error);
+ });
+ }
+ }
+
+ var localStorageWrapper = {
+ _driver: 'localStorageWrapper',
+ _initStorage: _initStorage,
+ // Default API, from Gaia/localStorage.
+ iterate: iterate,
+ getItem: getItem,
+ setItem: setItem,
+ removeItem: removeItem,
+ clear: clear,
+ length: length,
+ key: key,
+ keys: keys
+ };
+
+ if (moduleType === ModuleType.EXPORT) {
+ module.exports = localStorageWrapper;
+ } else if (moduleType === ModuleType.DEFINE) {
+ define('localStorageWrapper', function() {
+ return localStorageWrapper;
+ });
+ } else {
+ this.localStorageWrapper = localStorageWrapper;
+ }
+}).call(window);
+/*
+ * 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);
+(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);