UNPKG

react-localforage

Version:
1,549 lines (1,333 loc) 80.3 kB
import React from 'react'; import PropTypes from 'prop-types'; function commonjsRequire () { throw new Error('Dynamic requires are not currently supported by rollup-plugin-commonjs'); } function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var localforage = createCommonjsModule(function (module, exports) { /*! localForage -- Offline Storage, Improved Version 1.5.0 https://localforage.github.io/localForage (c) 2013-2017 Mozilla, Apache License 2.0 */ (function(f){{module.exports=f();}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof commonjsRequire=="function"&&commonjsRequire;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw (f.code="MODULE_NOT_FOUND", f)}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r);}return n[o].exports}var i=typeof commonjsRequire=="function"&&commonjsRequire;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ (function (global){ 'use strict'; var Mutation = global.MutationObserver || global.WebKitMutationObserver; var scheduleDrain; { if (Mutation) { var called = 0; var observer = new Mutation(nextTick); var element = global.document.createTextNode(''); observer.observe(element, { characterData: true }); scheduleDrain = function () { element.data = (called = ++called % 2); }; } else if (!global.setImmediate && typeof global.MessageChannel !== 'undefined') { var channel = new global.MessageChannel(); channel.port1.onmessage = nextTick; scheduleDrain = function () { channel.port2.postMessage(0); }; } else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) { scheduleDrain = function () { // Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted // into the document. Do so, thus queuing up the task. Remember to clean up once it's been called. var scriptEl = global.document.createElement('script'); scriptEl.onreadystatechange = function () { nextTick(); scriptEl.onreadystatechange = null; scriptEl.parentNode.removeChild(scriptEl); scriptEl = null; }; global.document.documentElement.appendChild(scriptEl); }; } else { scheduleDrain = function () { setTimeout(nextTick, 0); }; } } var draining; var queue = []; //named nextTick for less confusing stack traces function nextTick() { draining = true; var i, oldQueue; var len = queue.length; while (len) { oldQueue = queue; queue = []; i = -1; while (++i < len) { oldQueue[i](); } len = queue.length; } draining = false; } module.exports = immediate; function immediate(task) { if (queue.push(task) === 1 && !draining) { scheduleDrain(); } } }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}); },{}],2:[function(_dereq_,module,exports){ 'use strict'; var immediate = _dereq_(1); /* istanbul ignore next */ function INTERNAL() {} var handlers = {}; var REJECTED = ['REJECTED']; var FULFILLED = ['FULFILLED']; var PENDING = ['PENDING']; module.exports = exports = Promise; function Promise(resolver) { if (typeof resolver !== 'function') { throw new TypeError('resolver must be a function'); } this.state = PENDING; this.queue = []; this.outcome = void 0; if (resolver !== INTERNAL) { safelyResolveThenable(this, resolver); } } Promise.prototype["catch"] = function (onRejected) { return this.then(null, onRejected); }; Promise.prototype.then = function (onFulfilled, onRejected) { if (typeof onFulfilled !== 'function' && this.state === FULFILLED || typeof onRejected !== 'function' && this.state === REJECTED) { return this; } var promise = new this.constructor(INTERNAL); if (this.state !== PENDING) { var resolver = this.state === FULFILLED ? onFulfilled : onRejected; unwrap(promise, resolver, this.outcome); } else { this.queue.push(new QueueItem(promise, onFulfilled, onRejected)); } return promise; }; function QueueItem(promise, onFulfilled, onRejected) { this.promise = promise; if (typeof onFulfilled === 'function') { this.onFulfilled = onFulfilled; this.callFulfilled = this.otherCallFulfilled; } if (typeof onRejected === 'function') { this.onRejected = onRejected; this.callRejected = this.otherCallRejected; } } QueueItem.prototype.callFulfilled = function (value) { handlers.resolve(this.promise, value); }; QueueItem.prototype.otherCallFulfilled = function (value) { unwrap(this.promise, this.onFulfilled, value); }; QueueItem.prototype.callRejected = function (value) { handlers.reject(this.promise, value); }; QueueItem.prototype.otherCallRejected = function (value) { unwrap(this.promise, this.onRejected, value); }; function unwrap(promise, func, value) { immediate(function () { var returnValue; try { returnValue = func(value); } catch (e) { return handlers.reject(promise, e); } if (returnValue === promise) { handlers.reject(promise, new TypeError('Cannot resolve promise with itself')); } else { handlers.resolve(promise, returnValue); } }); } handlers.resolve = function (self, value) { var result = tryCatch(getThen, value); if (result.status === 'error') { return handlers.reject(self, result.value); } var thenable = result.value; if (thenable) { safelyResolveThenable(self, thenable); } else { self.state = FULFILLED; self.outcome = value; var i = -1; var len = self.queue.length; while (++i < len) { self.queue[i].callFulfilled(value); } } return self; }; handlers.reject = function (self, error) { self.state = REJECTED; self.outcome = error; var i = -1; var len = self.queue.length; while (++i < len) { self.queue[i].callRejected(error); } return self; }; function getThen(obj) { // Make sure we only access the accessor once as required by the spec var then = obj && obj.then; if (obj && typeof obj === 'object' && typeof then === 'function') { return function appyThen() { then.apply(obj, arguments); }; } } function safelyResolveThenable(self, thenable) { // Either fulfill, reject or reject with error var called = false; function onError(value) { if (called) { return; } called = true; handlers.reject(self, value); } function onSuccess(value) { if (called) { return; } called = true; handlers.resolve(self, value); } function tryToUnwrap() { thenable(onSuccess, onError); } var result = tryCatch(tryToUnwrap); if (result.status === 'error') { onError(result.value); } } function tryCatch(func, value) { var out = {}; try { out.value = func(value); out.status = 'success'; } catch (e) { out.status = 'error'; out.value = e; } return out; } exports.resolve = resolve; function resolve(value) { if (value instanceof this) { return value; } return handlers.resolve(new this(INTERNAL), value); } exports.reject = reject; function reject(reason) { var promise = new this(INTERNAL); return handlers.reject(promise, reason); } exports.all = all; function all(iterable) { var self = this; if (Object.prototype.toString.call(iterable) !== '[object Array]') { return this.reject(new TypeError('must be an array')); } var len = iterable.length; var called = false; if (!len) { return this.resolve([]); } var values = new Array(len); var resolved = 0; var i = -1; var promise = new this(INTERNAL); while (++i < len) { allResolver(iterable[i], i); } return promise; function allResolver(value, i) { self.resolve(value).then(resolveFromAll, function (error) { if (!called) { called = true; handlers.reject(promise, error); } }); function resolveFromAll(outValue) { values[i] = outValue; if (++resolved === len && !called) { called = true; handlers.resolve(promise, values); } } } } exports.race = race; function race(iterable) { var self = this; if (Object.prototype.toString.call(iterable) !== '[object Array]') { return this.reject(new TypeError('must be an array')); } var len = iterable.length; var called = false; if (!len) { return this.resolve([]); } var i = -1; var promise = new this(INTERNAL); while (++i < len) { resolver(iterable[i]); } return promise; function resolver(value) { self.resolve(value).then(function (response) { if (!called) { called = true; handlers.resolve(promise, response); } }, function (error) { if (!called) { called = true; handlers.reject(promise, error); } }); } } },{"1":1}],3:[function(_dereq_,module,exports){ (function (global){ 'use strict'; if (typeof global.Promise !== 'function') { global.Promise = _dereq_(2); } }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}); },{"2":2}],4:[function(_dereq_,module,exports){ 'use strict'; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function getIDB() { /* global indexedDB,webkitIndexedDB,mozIndexedDB,OIndexedDB,msIndexedDB */ try { if (typeof indexedDB !== 'undefined') { return indexedDB; } if (typeof webkitIndexedDB !== 'undefined') { return webkitIndexedDB; } if (typeof mozIndexedDB !== 'undefined') { return mozIndexedDB; } if (typeof OIndexedDB !== 'undefined') { return OIndexedDB; } if (typeof msIndexedDB !== 'undefined') { return msIndexedDB; } } catch (e) {} } var idb = getIDB(); function isIndexedDBValid() { try { // Initialize IndexedDB; fall back to vendor-prefixed versions // if needed. if (!idb) { return false; } // We mimic PouchDB here; // // We test for openDatabase because IE Mobile identifies itself // as Safari. Oh the lulz... var isSafari = typeof openDatabase !== 'undefined' && /(Safari|iPhone|iPad|iPod)/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent) && !/BlackBerry/.test(navigator.platform); var hasFetch = typeof fetch === 'function' && fetch.toString().indexOf('[native code') !== -1; // Safari <10.1 does not meet our requirements for IDB support (#5572) // since Safari 10.1 shipped with fetch, we can use that to detect it return (!isSafari || hasFetch) && typeof indexedDB !== 'undefined' && // some outdated implementations of IDB that appear on Samsung // and HTC Android devices <4.4 are missing IDBKeyRange typeof IDBKeyRange !== 'undefined'; } catch (e) { return false; } } function isWebSQLValid() { return typeof openDatabase === 'function'; } function isLocalStorageValid() { try { return typeof localStorage !== 'undefined' && 'setItem' in localStorage && localStorage.setItem; } catch (e) { return false; } } // Abstracts constructing a Blob object, so it also works in older // browsers that don't support the native Blob constructor. (i.e. // old QtWebKit versions, at least). // Abstracts constructing a Blob object, so it also works in older // browsers that don't support the native Blob constructor. (i.e. // old QtWebKit versions, at least). function createBlob(parts, properties) { /* global BlobBuilder,MSBlobBuilder,MozBlobBuilder,WebKitBlobBuilder */ parts = parts || []; properties = properties || {}; try { return new Blob(parts, properties); } catch (e) { if (e.name !== 'TypeError') { throw e; } var Builder = typeof BlobBuilder !== 'undefined' ? BlobBuilder : typeof MSBlobBuilder !== 'undefined' ? MSBlobBuilder : typeof MozBlobBuilder !== 'undefined' ? MozBlobBuilder : WebKitBlobBuilder; var builder = new Builder(); for (var i = 0; i < parts.length; i += 1) { builder.append(parts[i]); } return builder.getBlob(properties.type); } } // This is CommonJS because lie is an external dependency, so Rollup // can just ignore it. if (typeof Promise === 'undefined') { // In the "nopromises" build this will just throw if you don't have // a global promise object, but it would throw anyway later. _dereq_(3); } var Promise$1 = Promise; function executeCallback(promise, callback) { if (callback) { promise.then(function (result) { callback(null, result); }, function (error) { callback(error); }); } } function executeTwoCallbacks(promise, callback, errorCallback) { if (typeof callback === 'function') { promise.then(callback); } if (typeof errorCallback === 'function') { promise["catch"](errorCallback); } } // Some code originally from async_storage.js in // [Gaia](https://github.com/mozilla-b2g/gaia). var DETECT_BLOB_SUPPORT_STORE = 'local-forage-detect-blob-support'; var supportsBlobs; var dbContexts; var toString = Object.prototype.toString; // Transform a binary string to an array buffer, because otherwise // weird stuff happens when you try to work with the binary string directly. // It is known. // From http://stackoverflow.com/questions/14967647/ (continues on next line) // encode-decode-image-with-base64-breaks-image (2013-04-21) function _binStringToArrayBuffer(bin) { var length = bin.length; var buf = new ArrayBuffer(length); var arr = new Uint8Array(buf); for (var i = 0; i < length; i++) { arr[i] = bin.charCodeAt(i); } return buf; } // // Blobs are not supported in all versions of IndexedDB, notably // Chrome <37 and Android <5. In those versions, storing a blob will throw. // // Various other blob bugs exist in Chrome v37-42 (inclusive). // Detecting them is expensive and confusing to users, and Chrome 37-42 // is at very low usage worldwide, so we do a hacky userAgent check instead. // // content-type bug: https://code.google.com/p/chromium/issues/detail?id=408120 // 404 bug: https://code.google.com/p/chromium/issues/detail?id=447916 // FileReader bug: https://code.google.com/p/chromium/issues/detail?id=447836 // // Code borrowed from PouchDB. See: // https://github.com/pouchdb/pouchdb/blob/master/packages/node_modules/pouchdb-adapter-idb/src/blobSupport.js // function _checkBlobSupportWithoutCaching(idb) { return new Promise$1(function (resolve) { var txn = idb.transaction(DETECT_BLOB_SUPPORT_STORE, 'readwrite'); var blob = createBlob(['']); txn.objectStore(DETECT_BLOB_SUPPORT_STORE).put(blob, 'key'); txn.onabort = function (e) { // If the transaction aborts now its due to not being able to // write to the database, likely due to the disk being full e.preventDefault(); e.stopPropagation(); resolve(false); }; txn.oncomplete = function () { var matchedChrome = navigator.userAgent.match(/Chrome\/(\d+)/); var matchedEdge = navigator.userAgent.match(/Edge\//); // MS Edge pretends to be Chrome 42: // https://msdn.microsoft.com/en-us/library/hh869301%28v=vs.85%29.aspx resolve(matchedEdge || !matchedChrome || parseInt(matchedChrome[1], 10) >= 43); }; })["catch"](function () { return false; // error, so assume unsupported }); } function _checkBlobSupport(idb) { if (typeof supportsBlobs === 'boolean') { return Promise$1.resolve(supportsBlobs); } return _checkBlobSupportWithoutCaching(idb).then(function (value) { supportsBlobs = value; return supportsBlobs; }); } function _deferReadiness(dbInfo) { var dbContext = dbContexts[dbInfo.name]; // Create a deferred object representing the current database operation. var deferredOperation = {}; deferredOperation.promise = new Promise$1(function (resolve) { deferredOperation.resolve = resolve; }); // Enqueue the deferred operation. dbContext.deferredOperations.push(deferredOperation); // Chain its promise to the database readiness. if (!dbContext.dbReady) { dbContext.dbReady = deferredOperation.promise; } else { dbContext.dbReady = dbContext.dbReady.then(function () { return deferredOperation.promise; }); } } function _advanceReadiness(dbInfo) { var dbContext = dbContexts[dbInfo.name]; // Dequeue a deferred operation. var deferredOperation = dbContext.deferredOperations.pop(); // Resolve its promise (which is part of the database readiness // chain of promises). if (deferredOperation) { deferredOperation.resolve(); } } function _getConnection(dbInfo, upgradeNeeded) { return new Promise$1(function (resolve, reject) { if (dbInfo.db) { if (upgradeNeeded) { _deferReadiness(dbInfo); dbInfo.db.close(); } else { return resolve(dbInfo.db); } } var dbArgs = [dbInfo.name]; if (upgradeNeeded) { dbArgs.push(dbInfo.version); } var openreq = idb.open.apply(idb, dbArgs); if (upgradeNeeded) { openreq.onupgradeneeded = function (e) { var db = openreq.result; try { db.createObjectStore(dbInfo.storeName); if (e.oldVersion <= 1) { // Added when support for blob shims was added db.createObjectStore(DETECT_BLOB_SUPPORT_STORE); } } catch (ex) { if (ex.name === 'ConstraintError') { console.warn('The database "' + dbInfo.name + '"' + ' has been upgraded from version ' + e.oldVersion + ' to version ' + e.newVersion + ', but the storage "' + dbInfo.storeName + '" already exists.'); } else { throw ex; } } }; } openreq.onerror = function (e) { e.preventDefault(); reject(openreq.error); }; openreq.onsuccess = function () { resolve(openreq.result); _advanceReadiness(dbInfo); }; }); } function _getOriginalConnection(dbInfo) { return _getConnection(dbInfo, false); } function _getUpgradedConnection(dbInfo) { return _getConnection(dbInfo, true); } function _isUpgradeNeeded(dbInfo, defaultVersion) { if (!dbInfo.db) { return true; } var isNewStore = !dbInfo.db.objectStoreNames.contains(dbInfo.storeName); var isDowngrade = dbInfo.version < dbInfo.db.version; var isUpgrade = dbInfo.version > dbInfo.db.version; if (isDowngrade) { // If the version is not the default one // then warn for impossible downgrade. if (dbInfo.version !== defaultVersion) { console.warn('The database "' + dbInfo.name + '"' + ' can\'t be downgraded from version ' + dbInfo.db.version + ' to version ' + dbInfo.version + '.'); } // Align the versions to prevent errors. dbInfo.version = dbInfo.db.version; } if (isUpgrade || isNewStore) { // If the store is new then increment the version (if needed). // This will trigger an "upgradeneeded" event which is required // for creating a store. if (isNewStore) { var incVersion = dbInfo.db.version + 1; if (incVersion > dbInfo.version) { dbInfo.version = incVersion; } } return true; } return false; } // encode a blob for indexeddb engines that don't support blobs function _encodeBlob(blob) { return new Promise$1(function (resolve, reject) { var reader = new FileReader(); reader.onerror = reject; reader.onloadend = function (e) { var base64 = btoa(e.target.result || ''); resolve({ __local_forage_encoded_blob: true, data: base64, type: blob.type }); }; reader.readAsBinaryString(blob); }); } // decode an encoded blob function _decodeBlob(encodedBlob) { var arrayBuff = _binStringToArrayBuffer(atob(encodedBlob.data)); return createBlob([arrayBuff], { type: encodedBlob.type }); } // is this one of our fancy encoded blobs? function _isEncodedBlob(value) { return value && value.__local_forage_encoded_blob; } // Specialize the default `ready()` function by making it dependent // on the current database operations. Thus, the driver will be actually // ready when it's been initialized (default) *and* there are no pending // operations on the database (initiated by some other instances). function _fullyReady(callback) { var self = this; var promise = self._initReady().then(function () { var dbContext = dbContexts[self._dbInfo.name]; if (dbContext && dbContext.dbReady) { return dbContext.dbReady; } }); executeTwoCallbacks(promise, callback, callback); return promise; } // 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]; } } // Initialize a singleton container for all running localForages. if (!dbContexts) { dbContexts = {}; } // Get the current context of the database; var dbContext = dbContexts[dbInfo.name]; // ...or create a new context. if (!dbContext) { dbContext = { // Running localForages sharing a database. forages: [], // Shared database. db: null, // Database readiness (promise). dbReady: null, // Deferred operations on the database. deferredOperations: [] }; // Register the new context in the global container. dbContexts[dbInfo.name] = dbContext; } // Register itself as a running localForage in the current context. dbContext.forages.push(self); // Replace the default `ready()` function with the specialized one. if (!self._initReady) { self._initReady = self.ready; self.ready = _fullyReady; } // Create an array of initialization states of the related localForages. var initPromises = []; function ignoreErrors() { // Don't handle errors here, // just makes sure related localForages aren't pending. return Promise$1.resolve(); } for (var j = 0; j < dbContext.forages.length; j++) { var forage = dbContext.forages[j]; if (forage !== self) { // Don't wait for itself... initPromises.push(forage._initReady()["catch"](ignoreErrors)); } } // Take a snapshot of the related localForages. var forages = dbContext.forages.slice(0); // Initialize the connection process only when // all the related localForages aren't pending. return Promise$1.all(initPromises).then(function () { dbInfo.db = dbContext.db; // Get the connection or open a new one without upgrade. return _getOriginalConnection(dbInfo); }).then(function (db) { dbInfo.db = db; if (_isUpgradeNeeded(dbInfo, self._defaultConfig.version)) { // Reopen the database for upgrading. return _getUpgradedConnection(dbInfo); } return db; }).then(function (db) { dbInfo.db = dbContext.db = db; self._dbInfo = dbInfo; // Share the final connection amongst related localForages. for (var k = 0; k < forages.length; k++) { var forage = forages[k]; if (forage !== self) { // Self is already up-to-date. forage._dbInfo.db = dbInfo.db; forage._dbInfo.version = dbInfo.version; } } }); } 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') { console.warn(key + ' used as a key, but it is not a string.'); key = String(key); } var promise = new Promise$1(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; } if (_isEncodedBlob(value)) { value = _decodeBlob(value); } resolve(value); }; req.onerror = function () { reject(req.error); }; })["catch"](reject); }); executeCallback(promise, callback); return promise; } // Iterate over all items stored in database. function iterate(iterator, callback) { var self = this; var promise = new Promise$1(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 value = cursor.value; if (_isEncodedBlob(value)) { value = _decodeBlob(value); } var result = iterator(value, cursor.key, iterationNumber++); if (result !== void 0) { resolve(result); } else { cursor["continue"](); } } else { resolve(); } }; req.onerror = function () { reject(req.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') { console.warn(key + ' used as a key, but it is not a string.'); key = String(key); } var promise = new Promise$1(function (resolve, reject) { var dbInfo; self.ready().then(function () { dbInfo = self._dbInfo; if (toString.call(value) === '[object Blob]') { return _checkBlobSupport(dbInfo.db).then(function (blobSupport) { if (blobSupport) { return value; } return _encodeBlob(value); }); } return value; }).then(function (value) { var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite'); var store = transaction.objectStore(dbInfo.storeName); var req = store.put(value, key); // 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; } 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 () { var err = req.error ? req.error : req.transaction.error; reject(err); }; })["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') { console.warn(key + ' used as a key, but it is not a string.'); key = String(key); } var promise = new Promise$1(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 also be aborted if we've exceeded our storage // space. transaction.onabort = function () { var err = req.error ? req.error : req.transaction.error; reject(err); }; })["catch"](reject); }); executeCallback(promise, callback); return promise; } function clear(callback) { var self = this; var promise = new Promise$1(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 () { var err = req.error ? req.error : req.transaction.error; reject(err); }; })["catch"](reject); }); executeCallback(promise, callback); return promise; } function length(callback) { var self = this; var promise = new Promise$1(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$1(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$1(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; } var asyncStorage = { _driver: 'asyncStorage', _initStorage: _initStorage, iterate: iterate, getItem: getItem, setItem: setItem, removeItem: removeItem, clear: clear, length: length, key: key, keys: keys }; // 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 BLOB_TYPE_PREFIX = '~~local_forage_type~'; var BLOB_TYPE_PREFIX_REGEX = /^~~local_forage_type~([^~]+)~/; 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; var toString$1 = Object.prototype.toString; 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; } // 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 valueType = ''; if (value) { valueType = toString$1.call(value); } // 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 && (valueType === '[object ArrayBuffer]' || value.buffer && toString$1.call(value.buffer) === '[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 (valueType === '[object Int8Array]') { marker += TYPE_INT8ARRAY; } else if (valueType === '[object Uint8Array]') { marker += TYPE_UINT8ARRAY; } else if (valueType === '[object Uint8ClampedArray]') { marker += TYPE_UINT8CLAMPEDARRAY; } else if (valueType === '[object Int16Array]') { marker += TYPE_INT16ARRAY; } else if (valueType === '[object Uint16Array]') { marker += TYPE_UINT16ARRAY; } else if (valueType === '[object Int32Array]') { marker += TYPE_INT32ARRAY; } else if (valueType === '[object Uint32Array]') { marker += TYPE_UINT32ARRAY; } else if (valueType === '[object Float32Array]') { marker += TYPE_FLOAT32ARRAY; } else if (valueType === '[object Float64Array]') { marker += TYPE_FLOAT64ARRAY; } else { callback(new Error('Failed to get type for BinaryArray')); } } callback(marker + bufferToString(buffer)); } else if (valueType === '[object Blob]') { // Conver the blob to a binaryArray and then to a string. var fileReader = new FileReader(); fileReader.onload = function () { // Backwards-compatible prefix for the blob type. var str = BLOB_TYPE_PREFIX + value.type + '~' + bufferToString(this.result); callback(SERIALIZED_MARKER + TYPE_BLOB + str); }; fileReader.readAsArrayBuffer(value); } else { try { callback(JSON.stringify(value)); } catch (e) { 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 blobType; // Backwards-compatible blob type serialization strategy. // DBs created with older versions of localForage will simply not have the blob type. if (type === TYPE_BLOB && BLOB_TYPE_PREFIX_REGEX.test(serializedString)) { var matcher = serializedString.match(BLOB_TYPE_PREFIX_REGEX); blobType = matcher[1]; serializedString = serializedString.substring(matcher[0].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 createBlob([buffer], { type: blobType }); 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); } } var localforageSerializer = { serialize: serialize, deserialize: deserialize, stringToBuffer: stringToBuffer, bufferToString: bufferToString }; /* * Includes code from: * * base64-arraybuffer * https://github.com/niklasvh/base64-arraybuffer * * Copyright (c) 2012 Niklas von Hertzen * Licensed under the MIT license. */ // Open the WebSQL database (automatically creates one if one didn't // previously exist), using any options set in the config. function _initStorage$1(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 dbInfoPromise = new Promise$1(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 reject(e); } // 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); }); }); }); dbInfo.serializer = localforageSerializer; return dbInfoPromise; } function getItem$1(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') { console.warn(key + ' used as a key, but it is not a string.'); key = String(key); } var promise = new Promise$1(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 = dbInfo.serializer.deserialize(result); } resolve(result); }, function (t, error) { reject(error); }); }); })["catch"](reject); }); executeCallback(promise, callback); return promise; } function iterate$1(iterator, callback) { var self = this; var promise = new Promise$1(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 = dbInfo.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, retriesLeft) { var self = this; // Cast the key to a string, as that's all we can set as a key. if (typeof key !== 'string') { console.warn(key + ' used as a key, but it is not a string.'); key = String(key); } var promise = new Promise$1(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; var dbInfo = self._dbInfo; dbInfo.serializer.serialize(value, function (value, error) { if (error) { reject(error); } else { 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. // // Try to re-run the transaction. if (retriesLeft > 0) { resolve(_setItem.apply(self, [key, originalValue, callback, retriesLeft - 1])); return;