diff options
Diffstat (limited to 'public/editor/localforage')
| -rw-r--r-- | public/editor/localforage/localforage.js | 2497 | 
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); | 
