diff --git a/dist/localforage.js b/dist/localforage.js index 9fc8867..6c028ed 100644 --- a/dist/localforage.js +++ b/dist/localforage.js @@ -470,6 +470,10 @@ var supportsBlobs; var dbContexts; var toString = Object.prototype.toString; +// Transaction Modes +var READ_ONLY = 'readonly'; +var READ_WRITE = 'readwrite'; + // 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. @@ -502,7 +506,7 @@ function _binStringToArrayBuffer(bin) { // function _checkBlobSupportWithoutCaching(idb) { return new Promise$1(function (resolve) { - var txn = idb.transaction(DETECT_BLOB_SUPPORT_STORE, 'readwrite'); + var txn = idb.transaction(DETECT_BLOB_SUPPORT_STORE, READ_WRITE); var blob = createBlob(['']); txn.objectStore(DETECT_BLOB_SUPPORT_STORE).put(blob, 'key'); @@ -572,6 +576,19 @@ function _advanceReadiness(dbInfo) { } } +function _rejectReadiness(dbInfo, err) { + var dbContext = dbContexts[dbInfo.name]; + + // Dequeue a deferred operation. + var deferredOperation = dbContext.deferredOperations.pop(); + + // Reject its promise (which is part of the database readiness + // chain of promises). + if (deferredOperation) { + deferredOperation.reject(err); + } +} + function _getConnection(dbInfo, upgradeNeeded) { return new Promise$1(function (resolve, reject) { @@ -714,6 +731,51 @@ function _fullyReady(callback) { return promise; } +// Try to establish a new db connection to replace the +// current one which is broken (i.e. experiencing +// InvalidStateError while creating a transaction). +function _tryReconnect(dbInfo) { + _deferReadiness(dbInfo); + + var dbContext = dbContexts[dbInfo.name]; + var forages = dbContext.forages; + + for (var i = 0; i < forages.length; i++) { + if (forages[i]._dbInfo.db) { + forages[i]._dbInfo.db.close(); + forages[i]._dbInfo.db = null; + } + } + + return _getConnection(dbInfo, false).then(function (db) { + for (var j = 0; j < forages.length; j++) { + forages[j]._dbInfo.db = db; + } + })["catch"](function (err) { + _rejectReadiness(dbInfo, err); + throw err; + }); +} + +// FF doesn't like Promises (micro-tasks) and IDDB store operations, +// so we have to do it with callbacks +function createTransaction(dbInfo, mode, callback) { + try { + var tx = dbInfo.db.transaction(dbInfo.storeName, mode); + callback(null, tx); + } catch (err) { + if (!dbInfo.db || err.name === 'InvalidStateError') { + return _tryReconnect(dbInfo).then(function () { + + var tx = dbInfo.db.transaction(dbInfo.storeName, mode); + callback(null, tx); + }); + } + + callback(err); + } +} + // Open the IndexedDB database (automatically creates one if one didn't // previously exist), using any options set in the config. function _initStorage(options) { @@ -820,24 +882,33 @@ function getItem(key, callback) { 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; + createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) { + if (err) { + return reject(err); } - if (_isEncodedBlob(value)) { - value = _decodeBlob(value); - } - resolve(value); - }; - req.onerror = function () { - reject(req.error); - }; + try { + var store = transaction.objectStore(self._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 (e) { + reject(e); + } + }); })["catch"](reject); }); @@ -851,35 +922,46 @@ function iterate(iterator, callback) { 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(); + createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) { + if (err) { + return reject(err); } - }; - req.onerror = function () { - reject(req.error); - }; + try { + var store = transaction.objectStore(self._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++); + + // when the iterator callback retuns any + // (non-`undefined`) value, then we stop + // the iteration immediately + if (result !== void 0) { + resolve(result); + } else { + cursor["continue"](); + } + } else { + resolve(); + } + }; + + req.onerror = function () { + reject(req.error); + }; + } catch (e) { + reject(e); + } + }); })["catch"](reject); }); @@ -911,35 +993,44 @@ function setItem(key, value, callback) { } 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; + createTransaction(self._dbInfo, READ_WRITE, function (err, transaction) { + if (err) { + return reject(err); } - resolve(value); - }; - transaction.onabort = transaction.onerror = function () { - var err = req.error ? req.error : req.transaction.error; - reject(err); - }; + try { + var store = transaction.objectStore(self._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 (e) { + reject(e); + } + }); })["catch"](reject); }); @@ -958,30 +1049,37 @@ function removeItem(key, callback) { 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); + createTransaction(self._dbInfo, READ_WRITE, function (err, transaction) { + if (err) { + return reject(err); + } - // 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(); - }; + try { + var store = transaction.objectStore(self._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); - }; + 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); - }; + // 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 (e) { + reject(e); + } + }); })["catch"](reject); }); @@ -994,19 +1092,27 @@ function clear(callback) { 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(); + createTransaction(self._dbInfo, READ_WRITE, function (err, transaction) { + if (err) { + return reject(err); + } - transaction.oncomplete = function () { - resolve(); - }; + try { + var store = transaction.objectStore(self._dbInfo.storeName); + var req = store.clear(); - transaction.onabort = transaction.onerror = function () { - var err = req.error ? req.error : req.transaction.error; - reject(err); - }; + transaction.oncomplete = function () { + resolve(); + }; + + transaction.onabort = transaction.onerror = function () { + var err = req.error ? req.error : req.transaction.error; + reject(err); + }; + } catch (e) { + reject(e); + } + }); })["catch"](reject); }); @@ -1019,17 +1125,26 @@ function length(callback) { 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(); + createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) { + if (err) { + return reject(err); + } - req.onsuccess = function () { - resolve(req.result); - }; + try { + var store = transaction.objectStore(self._dbInfo.storeName); + var req = store.count(); - req.onerror = function () { - reject(req.error); - }; + req.onsuccess = function () { + resolve(req.result); + }; + + req.onerror = function () { + reject(req.error); + }; + } catch (e) { + reject(e); + } + }); })["catch"](reject); }); @@ -1048,40 +1163,49 @@ function key(n, callback) { } 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; + createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) { + if (err) { + return reject(err); } - 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); - } - } - }; + try { + var store = transaction.objectStore(self._dbInfo.storeName); + var advanced = false; + var req = store.openCursor(); - req.onerror = function () { - reject(req.error); - }; + 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 (e) { + reject(e); + } + }); })["catch"](reject); }); @@ -1094,27 +1218,35 @@ function keys(callback) { 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; + createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) { + if (err) { + return reject(err); } - keys.push(cursor.key); - cursor["continue"](); - }; + try { + var store = transaction.objectStore(self._dbInfo.storeName); + var req = store.openCursor(); + var keys = []; - req.onerror = function () { - reject(req.error); - }; + 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 (e) { + reject(e); + } + }); })["catch"](reject); }); diff --git a/dist/localforage.min.js b/dist/localforage.min.js index db9faa4..8c4142c 100644 --- a/dist/localforage.min.js +++ b/dist/localforage.min.js @@ -4,4 +4,4 @@ https://localforage.github.io/localForage (c) 2013-2017 Mozilla, Apache License 2.0 */ -!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.localforage=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c||a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g=43)}}).catch(function(){return!1})}function n(a){return"boolean"==typeof ha?ja.resolve(ha):m(a).then(function(a){return ha=a})}function o(a){var b=ia[a.name],c={};c.promise=new ja(function(a){c.resolve=a}),b.deferredOperations.push(c),b.dbReady?b.dbReady=b.dbReady.then(function(){return c.promise}):b.dbReady=c.promise}function p(a){var b=ia[a.name],c=b.deferredOperations.pop();c&&c.resolve()}function q(a,b){return new ja(function(c,d){if(a.db){if(!b)return c(a.db);o(a),a.db.close()}var e=[a.name];b&&e.push(a.version);var f=ga.open.apply(ga,e);b&&(f.onupgradeneeded=function(b){var c=f.result;try{c.createObjectStore(a.storeName),b.oldVersion<=1&&c.createObjectStore(ka)}catch(c){if("ConstraintError"!==c.name)throw c;console.warn('The database "'+a.name+'" has been upgraded from version '+b.oldVersion+" to version "+b.newVersion+', but the storage "'+a.storeName+'" already exists.')}}),f.onerror=function(a){a.preventDefault(),d(f.error)},f.onsuccess=function(){c(f.result),p(a)}})}function r(a){return q(a,!1)}function s(a){return q(a,!0)}function t(a,b){if(!a.db)return!0;var c=!a.db.objectStoreNames.contains(a.storeName),d=a.versiona.db.version;if(d&&(a.version!==b&&console.warn('The database "'+a.name+"\" can't be downgraded from version "+a.db.version+" to version "+a.version+"."),a.version=a.db.version),e||c){if(c){var f=a.db.version+1;f>a.version&&(a.version=f)}return!0}return!1}function u(a){return new ja(function(b,c){var d=new FileReader;d.onerror=c,d.onloadend=function(c){var d=btoa(c.target.result||"");b({__local_forage_encoded_blob:!0,data:d,type:a.type})},d.readAsBinaryString(a)})}function v(a){return i([l(atob(a.data))],{type:a.type})}function w(a){return a&&a.__local_forage_encoded_blob}function x(a){var b=this,c=b._initReady().then(function(){var a=ia[b._dbInfo.name];if(a&&a.dbReady)return a.dbReady});return k(c,a,a),c}function y(a){function b(){return ja.resolve()}var c=this,d={db:null};if(a)for(var e in a)d[e]=a[e];ia||(ia={});var f=ia[d.name];f||(f={forages:[],db:null,dbReady:null,deferredOperations:[]},ia[d.name]=f),f.forages.push(c),c._initReady||(c._initReady=c.ready,c.ready=x);for(var g=[],h=0;h>4,k[i++]=(15&d)<<4|e>>2,k[i++]=(3&e)<<6|63&f;return j}function I(a){var b,c=new Uint8Array(a),d="";for(b=0;b>2],d+=na[(3&c[b])<<4|c[b+1]>>4],d+=na[(15&c[b+1])<<2|c[b+2]>>6],d+=na[63&c[b+2]];return c.length%3==2?d=d.substring(0,d.length-1)+"=":c.length%3==1&&(d=d.substring(0,d.length-2)+"=="),d}function J(a,b){var c="";if(a&&(c=Ea.call(a)),a&&("[object ArrayBuffer]"===c||a.buffer&&"[object ArrayBuffer]"===Ea.call(a.buffer))){var d,e=qa;a instanceof ArrayBuffer?(d=a,e+=sa):(d=a.buffer,"[object Int8Array]"===c?e+=ua:"[object Uint8Array]"===c?e+=va:"[object Uint8ClampedArray]"===c?e+=wa:"[object Int16Array]"===c?e+=xa:"[object Uint16Array]"===c?e+=za:"[object Int32Array]"===c?e+=ya:"[object Uint32Array]"===c?e+=Aa:"[object Float32Array]"===c?e+=Ba:"[object Float64Array]"===c?e+=Ca:b(new Error("Failed to get type for BinaryArray"))),b(e+I(d))}else if("[object Blob]"===c){var f=new FileReader;f.onload=function(){var c=oa+a.type+"~"+I(this.result);b(qa+ta+c)},f.readAsArrayBuffer(a)}else try{b(JSON.stringify(a))}catch(c){console.error("Couldn't convert value into a JSON string: ",a),b(null,c)}}function K(a){if(a.substring(0,ra)!==qa)return JSON.parse(a);var b,c=a.substring(Da),d=a.substring(ra,Da);if(d===ta&&pa.test(c)){var e=c.match(pa);b=e[1],c=c.substring(e[0].length)}var f=H(c);switch(d){case sa:return f;case ta:return i([f],{type:b});case ua:return new Int8Array(f);case va:return new Uint8Array(f);case wa:return new Uint8ClampedArray(f);case xa:return new Int16Array(f);case za:return new Uint16Array(f);case ya:return new Int32Array(f);case Aa:return new Uint32Array(f);case Ba:return new Float32Array(f);case Ca:return new Float64Array(f);default:throw new Error("Unkown type: "+d)}}function L(a){var b=this,c={db:null};if(a)for(var d in a)c[d]="string"!=typeof a[d]?a[d].toString():a[d];var e=new ja(function(a,d){try{c.db=openDatabase(c.name,String(c.version),c.description,c.size)}catch(a){return d(a)}c.db.transaction(function(e){e.executeSql("CREATE TABLE IF NOT EXISTS "+c.storeName+" (id INTEGER PRIMARY KEY, key unique, value)",[],function(){b._dbInfo=c,a()},function(a,b){d(b)})})});return c.serializer=Fa,e}function M(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT * FROM "+e.storeName+" WHERE key = ? LIMIT 1",[a],function(a,c){var d=c.rows.length?c.rows.item(0).value:null;d&&(d=e.serializer.deserialize(d)),b(d)},function(a,b){d(b)})})}).catch(d)});return j(d,b),d}function N(a,b){var c=this,d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT * FROM "+e.storeName,[],function(c,d){for(var f=d.rows,g=f.length,h=0;h0)return void f(O.apply(e,[a,h,c,d-1]));g(b)}})})}).catch(g)});return j(f,c),f}function P(a,b,c){return O.apply(this,[a,b,c,1])}function Q(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("DELETE FROM "+e.storeName+" WHERE key = ?",[a],function(){b()},function(a,b){d(b)})})}).catch(d)});return j(d,b),d}function R(a){var b=this,c=new ja(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("DELETE FROM "+d.storeName,[],function(){a()},function(a,b){c(b)})})}).catch(c)});return j(c,a),c}function S(a){var b=this,c=new ja(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("SELECT COUNT(key) as c FROM "+d.storeName,[],function(b,c){var d=c.rows.item(0).c;a(d)},function(a,b){c(b)})})}).catch(c)});return j(c,a),c}function T(a,b){var c=this,d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT key FROM "+e.storeName+" WHERE id = ? LIMIT 1",[a+1],function(a,c){var d=c.rows.length?c.rows.item(0).key:null;b(d)},function(a,b){d(b)})})}).catch(d)});return j(d,b),d}function U(a){var b=this,c=new ja(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("SELECT key FROM "+d.storeName,[],function(b,c){for(var d=[],e=0;e=0;c--){var d=localStorage.key(c);0===d.indexOf(a)&&localStorage.removeItem(d)}});return j(c,a),c}function X(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=c.ready().then(function(){var b=c._dbInfo,d=localStorage.getItem(b.keyPrefix+a);return d&&(d=b.serializer.deserialize(d)),d});return j(d,b),d}function Y(a,b){var c=this,d=c.ready().then(function(){for(var b=c._dbInfo,d=b.keyPrefix,e=d.length,f=localStorage.length,g=1,h=0;h=43)}}).catch(function(){return!1})}function n(a){return"boolean"==typeof ka?ma.resolve(ka):m(a).then(function(a){return ka=a})}function o(a){var b=la[a.name],c={};c.promise=new ma(function(a){c.resolve=a}),b.deferredOperations.push(c),b.dbReady?b.dbReady=b.dbReady.then(function(){return c.promise}):b.dbReady=c.promise}function p(a){var b=la[a.name],c=b.deferredOperations.pop();c&&c.resolve()}function q(a,b){var c=la[a.name],d=c.deferredOperations.pop();d&&d.reject(b)}function r(a,b){return new ma(function(c,d){if(a.db){if(!b)return c(a.db);o(a),a.db.close()}var e=[a.name];b&&e.push(a.version);var f=ja.open.apply(ja,e);b&&(f.onupgradeneeded=function(b){var c=f.result;try{c.createObjectStore(a.storeName),b.oldVersion<=1&&c.createObjectStore(na)}catch(c){if("ConstraintError"!==c.name)throw c;console.warn('The database "'+a.name+'" has been upgraded from version '+b.oldVersion+" to version "+b.newVersion+', but the storage "'+a.storeName+'" already exists.')}}),f.onerror=function(a){a.preventDefault(),d(f.error)},f.onsuccess=function(){c(f.result),p(a)}})}function s(a){return r(a,!1)}function t(a){return r(a,!0)}function u(a,b){if(!a.db)return!0;var c=!a.db.objectStoreNames.contains(a.storeName),d=a.versiona.db.version;if(d&&(a.version!==b&&console.warn('The database "'+a.name+"\" can't be downgraded from version "+a.db.version+" to version "+a.version+"."),a.version=a.db.version),e||c){if(c){var f=a.db.version+1;f>a.version&&(a.version=f)}return!0}return!1}function v(a){return new ma(function(b,c){var d=new FileReader;d.onerror=c,d.onloadend=function(c){var d=btoa(c.target.result||"");b({__local_forage_encoded_blob:!0,data:d,type:a.type})},d.readAsBinaryString(a)})}function w(a){return i([l(atob(a.data))],{type:a.type})}function x(a){return a&&a.__local_forage_encoded_blob}function y(a){var b=this,c=b._initReady().then(function(){var a=la[b._dbInfo.name];if(a&&a.dbReady)return a.dbReady});return k(c,a,a),c}function z(a){o(a);for(var b=la[a.name],c=b.forages,d=0;d>4,k[i++]=(15&d)<<4|e>>2,k[i++]=(3&e)<<6|63&f;return j}function L(a){var b,c=new Uint8Array(a),d="";for(b=0;b>2],d+=sa[(3&c[b])<<4|c[b+1]>>4],d+=sa[(15&c[b+1])<<2|c[b+2]>>6],d+=sa[63&c[b+2]];return c.length%3==2?d=d.substring(0,d.length-1)+"=":c.length%3==1&&(d=d.substring(0,d.length-2)+"=="),d}function M(a,b){var c="";if(a&&(c=Ja.call(a)),a&&("[object ArrayBuffer]"===c||a.buffer&&"[object ArrayBuffer]"===Ja.call(a.buffer))){var d,e=va;a instanceof ArrayBuffer?(d=a,e+=xa):(d=a.buffer,"[object Int8Array]"===c?e+=za:"[object Uint8Array]"===c?e+=Aa:"[object Uint8ClampedArray]"===c?e+=Ba:"[object Int16Array]"===c?e+=Ca:"[object Uint16Array]"===c?e+=Ea:"[object Int32Array]"===c?e+=Da:"[object Uint32Array]"===c?e+=Fa:"[object Float32Array]"===c?e+=Ga:"[object Float64Array]"===c?e+=Ha:b(new Error("Failed to get type for BinaryArray"))),b(e+L(d))}else if("[object Blob]"===c){var f=new FileReader;f.onload=function(){var c=ta+a.type+"~"+L(this.result);b(va+ya+c)},f.readAsArrayBuffer(a)}else try{b(JSON.stringify(a))}catch(c){console.error("Couldn't convert value into a JSON string: ",a),b(null,c)}}function N(a){if(a.substring(0,wa)!==va)return JSON.parse(a);var b,c=a.substring(Ia),d=a.substring(wa,Ia);if(d===ya&&ua.test(c)){var e=c.match(ua);b=e[1],c=c.substring(e[0].length)}var f=K(c);switch(d){case xa:return f;case ya:return i([f],{type:b});case za:return new Int8Array(f);case Aa:return new Uint8Array(f);case Ba:return new Uint8ClampedArray(f);case Ca:return new Int16Array(f);case Ea:return new Uint16Array(f);case Da:return new Int32Array(f);case Fa:return new Uint32Array(f);case Ga:return new Float32Array(f);case Ha:return new Float64Array(f);default:throw new Error("Unkown type: "+d)}}function O(a){var b=this,c={db:null};if(a)for(var d in a)c[d]="string"!=typeof a[d]?a[d].toString():a[d];var e=new ma(function(a,d){try{c.db=openDatabase(c.name,String(c.version),c.description,c.size)}catch(a){return d(a)}c.db.transaction(function(e){e.executeSql("CREATE TABLE IF NOT EXISTS "+c.storeName+" (id INTEGER PRIMARY KEY, key unique, value)",[],function(){b._dbInfo=c,a()},function(a,b){d(b)})})});return c.serializer=Ka,e}function P(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=new ma(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT * FROM "+e.storeName+" WHERE key = ? LIMIT 1",[a],function(a,c){var d=c.rows.length?c.rows.item(0).value:null;d&&(d=e.serializer.deserialize(d)),b(d)},function(a,b){d(b)})})}).catch(d)});return j(d,b),d}function Q(a,b){var c=this,d=new ma(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT * FROM "+e.storeName,[],function(c,d){for(var f=d.rows,g=f.length,h=0;h0)return void f(R.apply(e,[a,h,c,d-1]));g(b)}})})}).catch(g)});return j(f,c),f}function S(a,b,c){return R.apply(this,[a,b,c,1])}function T(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=new ma(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("DELETE FROM "+e.storeName+" WHERE key = ?",[a],function(){b()},function(a,b){d(b)})})}).catch(d)});return j(d,b),d}function U(a){var b=this,c=new ma(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("DELETE FROM "+d.storeName,[],function(){a()},function(a,b){c(b)})})}).catch(c)});return j(c,a),c}function V(a){var b=this,c=new ma(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("SELECT COUNT(key) as c FROM "+d.storeName,[],function(b,c){var d=c.rows.item(0).c;a(d)},function(a,b){c(b)})})}).catch(c)});return j(c,a),c}function W(a,b){var c=this,d=new ma(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT key FROM "+e.storeName+" WHERE id = ? LIMIT 1",[a+1],function(a,c){var d=c.rows.length?c.rows.item(0).key:null;b(d)},function(a,b){d(b)})})}).catch(d)});return j(d,b),d}function X(a){var b=this,c=new ma(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("SELECT key FROM "+d.storeName,[],function(b,c){for(var d=[],e=0;e=0;c--){var d=localStorage.key(c);0===d.indexOf(a)&&localStorage.removeItem(d)}});return j(c,a),c}function $(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=c.ready().then(function(){var b=c._dbInfo,d=localStorage.getItem(b.keyPrefix+a);return d&&(d=b.serializer.deserialize(d)),d});return j(d,b),d}function _(a,b){var c=this,d=c.ready().then(function(){for(var b=c._dbInfo,d=b.keyPrefix,e=d.length,f=localStorage.length,g=1,h=0;h=43)}}).catch(function(){return!1})}function n(a){return"boolean"==typeof ha?ja.resolve(ha):m(a).then(function(a){return ha=a})}function o(a){var b=ia[a.name],c={};c.promise=new ja(function(a){c.resolve=a}),b.deferredOperations.push(c),b.dbReady?b.dbReady=b.dbReady.then(function(){return c.promise}):b.dbReady=c.promise}function p(a){var b=ia[a.name],c=b.deferredOperations.pop();c&&c.resolve()}function q(a,b){return new ja(function(c,d){if(a.db){if(!b)return c(a.db);o(a),a.db.close()}var e=[a.name];b&&e.push(a.version);var f=ga.open.apply(ga,e);b&&(f.onupgradeneeded=function(b){var c=f.result;try{c.createObjectStore(a.storeName),b.oldVersion<=1&&c.createObjectStore(ka)}catch(c){if("ConstraintError"!==c.name)throw c;console.warn('The database "'+a.name+'" has been upgraded from version '+b.oldVersion+" to version "+b.newVersion+', but the storage "'+a.storeName+'" already exists.')}}),f.onerror=function(a){a.preventDefault(),d(f.error)},f.onsuccess=function(){c(f.result),p(a)}})}function r(a){return q(a,!1)}function s(a){return q(a,!0)}function t(a,b){if(!a.db)return!0;var c=!a.db.objectStoreNames.contains(a.storeName),d=a.versiona.db.version;if(d&&(a.version!==b&&console.warn('The database "'+a.name+"\" can't be downgraded from version "+a.db.version+" to version "+a.version+"."),a.version=a.db.version),e||c){if(c){var f=a.db.version+1;f>a.version&&(a.version=f)}return!0}return!1}function u(a){return new ja(function(b,c){var d=new FileReader;d.onerror=c,d.onloadend=function(c){var d=btoa(c.target.result||"");b({__local_forage_encoded_blob:!0,data:d,type:a.type})},d.readAsBinaryString(a)})}function v(a){return i([l(atob(a.data))],{type:a.type})}function w(a){return a&&a.__local_forage_encoded_blob}function x(a){var b=this,c=b._initReady().then(function(){var a=ia[b._dbInfo.name];if(a&&a.dbReady)return a.dbReady});return k(c,a,a),c}function y(a){function b(){return ja.resolve()}var c=this,d={db:null};if(a)for(var e in a)d[e]=a[e];ia||(ia={});var f=ia[d.name];f||(f={forages:[],db:null,dbReady:null,deferredOperations:[]},ia[d.name]=f),f.forages.push(c),c._initReady||(c._initReady=c.ready,c.ready=x);for(var g=[],h=0;h>4,k[i++]=(15&d)<<4|e>>2,k[i++]=(3&e)<<6|63&f;return j}function I(a){var b,c=new Uint8Array(a),d="";for(b=0;b>2],d+=na[(3&c[b])<<4|c[b+1]>>4],d+=na[(15&c[b+1])<<2|c[b+2]>>6],d+=na[63&c[b+2]];return c.length%3==2?d=d.substring(0,d.length-1)+"=":c.length%3==1&&(d=d.substring(0,d.length-2)+"=="),d}function J(a,b){var c="";if(a&&(c=Ea.call(a)),a&&("[object ArrayBuffer]"===c||a.buffer&&"[object ArrayBuffer]"===Ea.call(a.buffer))){var d,e=qa;a instanceof ArrayBuffer?(d=a,e+=sa):(d=a.buffer,"[object Int8Array]"===c?e+=ua:"[object Uint8Array]"===c?e+=va:"[object Uint8ClampedArray]"===c?e+=wa:"[object Int16Array]"===c?e+=xa:"[object Uint16Array]"===c?e+=za:"[object Int32Array]"===c?e+=ya:"[object Uint32Array]"===c?e+=Aa:"[object Float32Array]"===c?e+=Ba:"[object Float64Array]"===c?e+=Ca:b(new Error("Failed to get type for BinaryArray"))),b(e+I(d))}else if("[object Blob]"===c){var f=new FileReader;f.onload=function(){var c=oa+a.type+"~"+I(this.result);b(qa+ta+c)},f.readAsArrayBuffer(a)}else try{b(JSON.stringify(a))}catch(c){console.error("Couldn't convert value into a JSON string: ",a),b(null,c)}}function K(a){if(a.substring(0,ra)!==qa)return JSON.parse(a);var b,c=a.substring(Da),d=a.substring(ra,Da);if(d===ta&&pa.test(c)){var e=c.match(pa);b=e[1],c=c.substring(e[0].length)}var f=H(c);switch(d){case sa:return f;case ta:return i([f],{type:b});case ua:return new Int8Array(f);case va:return new Uint8Array(f);case wa:return new Uint8ClampedArray(f);case xa:return new Int16Array(f);case za:return new Uint16Array(f);case ya:return new Int32Array(f);case Aa:return new Uint32Array(f);case Ba:return new Float32Array(f);case Ca:return new Float64Array(f);default:throw new Error("Unkown type: "+d)}}function L(a){var b=this,c={db:null};if(a)for(var d in a)c[d]="string"!=typeof a[d]?a[d].toString():a[d];var e=new ja(function(a,d){try{c.db=openDatabase(c.name,String(c.version),c.description,c.size)}catch(a){return d(a)}c.db.transaction(function(e){e.executeSql("CREATE TABLE IF NOT EXISTS "+c.storeName+" (id INTEGER PRIMARY KEY, key unique, value)",[],function(){b._dbInfo=c,a()},function(a,b){d(b)})})});return c.serializer=Fa,e}function M(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT * FROM "+e.storeName+" WHERE key = ? LIMIT 1",[a],function(a,c){var d=c.rows.length?c.rows.item(0).value:null;d&&(d=e.serializer.deserialize(d)),b(d)},function(a,b){d(b)})})}).catch(d)});return j(d,b),d}function N(a,b){var c=this,d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT * FROM "+e.storeName,[],function(c,d){for(var f=d.rows,g=f.length,h=0;h0)return void f(O.apply(e,[a,h,c,d-1]));g(b)}})})}).catch(g)});return j(f,c),f}function P(a,b,c){return O.apply(this,[a,b,c,1])}function Q(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("DELETE FROM "+e.storeName+" WHERE key = ?",[a],function(){b()},function(a,b){d(b)})})}).catch(d)});return j(d,b),d}function R(a){var b=this,c=new ja(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("DELETE FROM "+d.storeName,[],function(){a()},function(a,b){c(b)})})}).catch(c)});return j(c,a),c}function S(a){var b=this,c=new ja(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("SELECT COUNT(key) as c FROM "+d.storeName,[],function(b,c){var d=c.rows.item(0).c;a(d)},function(a,b){c(b)})})}).catch(c)});return j(c,a),c}function T(a,b){var c=this,d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT key FROM "+e.storeName+" WHERE id = ? LIMIT 1",[a+1],function(a,c){var d=c.rows.length?c.rows.item(0).key:null;b(d)},function(a,b){d(b)})})}).catch(d)});return j(d,b),d}function U(a){var b=this,c=new ja(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("SELECT key FROM "+d.storeName,[],function(b,c){for(var d=[],e=0;e=0;c--){var d=localStorage.key(c);0===d.indexOf(a)&&localStorage.removeItem(d)}});return j(c,a),c}function X(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=c.ready().then(function(){var b=c._dbInfo,d=localStorage.getItem(b.keyPrefix+a);return d&&(d=b.serializer.deserialize(d)),d});return j(d,b),d}function Y(a,b){var c=this,d=c.ready().then(function(){for(var b=c._dbInfo,d=b.keyPrefix,e=d.length,f=localStorage.length,g=1,h=0;h=43)}}).catch(function(){return!1})}function n(a){return"boolean"==typeof ka?ma.resolve(ka):m(a).then(function(a){return ka=a})}function o(a){var b=la[a.name],c={};c.promise=new ma(function(a){c.resolve=a}),b.deferredOperations.push(c),b.dbReady?b.dbReady=b.dbReady.then(function(){return c.promise}):b.dbReady=c.promise}function p(a){var b=la[a.name],c=b.deferredOperations.pop();c&&c.resolve()}function q(a,b){var c=la[a.name],d=c.deferredOperations.pop();d&&d.reject(b)}function r(a,b){return new ma(function(c,d){if(a.db){if(!b)return c(a.db);o(a),a.db.close()}var e=[a.name];b&&e.push(a.version);var f=ja.open.apply(ja,e);b&&(f.onupgradeneeded=function(b){var c=f.result;try{c.createObjectStore(a.storeName),b.oldVersion<=1&&c.createObjectStore(na)}catch(c){if("ConstraintError"!==c.name)throw c;console.warn('The database "'+a.name+'" has been upgraded from version '+b.oldVersion+" to version "+b.newVersion+', but the storage "'+a.storeName+'" already exists.')}}),f.onerror=function(a){a.preventDefault(),d(f.error)},f.onsuccess=function(){c(f.result),p(a)}})}function s(a){return r(a,!1)}function t(a){return r(a,!0)}function u(a,b){if(!a.db)return!0;var c=!a.db.objectStoreNames.contains(a.storeName),d=a.versiona.db.version;if(d&&(a.version!==b&&console.warn('The database "'+a.name+"\" can't be downgraded from version "+a.db.version+" to version "+a.version+"."),a.version=a.db.version),e||c){if(c){var f=a.db.version+1;f>a.version&&(a.version=f)}return!0}return!1}function v(a){return new ma(function(b,c){var d=new FileReader;d.onerror=c,d.onloadend=function(c){var d=btoa(c.target.result||"");b({__local_forage_encoded_blob:!0,data:d,type:a.type})},d.readAsBinaryString(a)})}function w(a){return i([l(atob(a.data))],{type:a.type})}function x(a){return a&&a.__local_forage_encoded_blob}function y(a){var b=this,c=b._initReady().then(function(){var a=la[b._dbInfo.name];if(a&&a.dbReady)return a.dbReady});return k(c,a,a),c}function z(a){o(a);for(var b=la[a.name],c=b.forages,d=0;d>4,k[i++]=(15&d)<<4|e>>2,k[i++]=(3&e)<<6|63&f;return j}function L(a){var b,c=new Uint8Array(a),d="";for(b=0;b>2],d+=sa[(3&c[b])<<4|c[b+1]>>4],d+=sa[(15&c[b+1])<<2|c[b+2]>>6],d+=sa[63&c[b+2]];return c.length%3==2?d=d.substring(0,d.length-1)+"=":c.length%3==1&&(d=d.substring(0,d.length-2)+"=="),d}function M(a,b){var c="";if(a&&(c=Ja.call(a)),a&&("[object ArrayBuffer]"===c||a.buffer&&"[object ArrayBuffer]"===Ja.call(a.buffer))){var d,e=va;a instanceof ArrayBuffer?(d=a,e+=xa):(d=a.buffer,"[object Int8Array]"===c?e+=za:"[object Uint8Array]"===c?e+=Aa:"[object Uint8ClampedArray]"===c?e+=Ba:"[object Int16Array]"===c?e+=Ca:"[object Uint16Array]"===c?e+=Ea:"[object Int32Array]"===c?e+=Da:"[object Uint32Array]"===c?e+=Fa:"[object Float32Array]"===c?e+=Ga:"[object Float64Array]"===c?e+=Ha:b(new Error("Failed to get type for BinaryArray"))),b(e+L(d))}else if("[object Blob]"===c){var f=new FileReader;f.onload=function(){var c=ta+a.type+"~"+L(this.result);b(va+ya+c)},f.readAsArrayBuffer(a)}else try{b(JSON.stringify(a))}catch(c){console.error("Couldn't convert value into a JSON string: ",a),b(null,c)}}function N(a){if(a.substring(0,wa)!==va)return JSON.parse(a);var b,c=a.substring(Ia),d=a.substring(wa,Ia);if(d===ya&&ua.test(c)){var e=c.match(ua);b=e[1],c=c.substring(e[0].length)}var f=K(c);switch(d){case xa:return f;case ya:return i([f],{type:b});case za:return new Int8Array(f);case Aa:return new Uint8Array(f);case Ba:return new Uint8ClampedArray(f);case Ca:return new Int16Array(f);case Ea:return new Uint16Array(f);case Da:return new Int32Array(f);case Fa:return new Uint32Array(f);case Ga:return new Float32Array(f);case Ha:return new Float64Array(f);default:throw new Error("Unkown type: "+d)}}function O(a){var b=this,c={db:null};if(a)for(var d in a)c[d]="string"!=typeof a[d]?a[d].toString():a[d];var e=new ma(function(a,d){try{c.db=openDatabase(c.name,String(c.version),c.description,c.size)}catch(a){return d(a)}c.db.transaction(function(e){e.executeSql("CREATE TABLE IF NOT EXISTS "+c.storeName+" (id INTEGER PRIMARY KEY, key unique, value)",[],function(){b._dbInfo=c,a()},function(a,b){d(b)})})});return c.serializer=Ka,e}function P(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=new ma(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT * FROM "+e.storeName+" WHERE key = ? LIMIT 1",[a],function(a,c){var d=c.rows.length?c.rows.item(0).value:null;d&&(d=e.serializer.deserialize(d)),b(d)},function(a,b){d(b)})})}).catch(d)});return j(d,b),d}function Q(a,b){var c=this,d=new ma(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT * FROM "+e.storeName,[],function(c,d){for(var f=d.rows,g=f.length,h=0;h0)return void f(R.apply(e,[a,h,c,d-1]));g(b)}})})}).catch(g)});return j(f,c),f}function S(a,b,c){return R.apply(this,[a,b,c,1])}function T(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=new ma(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("DELETE FROM "+e.storeName+" WHERE key = ?",[a],function(){b()},function(a,b){d(b)})})}).catch(d)});return j(d,b),d}function U(a){var b=this,c=new ma(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("DELETE FROM "+d.storeName,[],function(){a()},function(a,b){c(b)})})}).catch(c)});return j(c,a),c}function V(a){var b=this,c=new ma(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("SELECT COUNT(key) as c FROM "+d.storeName,[],function(b,c){var d=c.rows.item(0).c;a(d)},function(a,b){c(b)})})}).catch(c)});return j(c,a),c}function W(a,b){var c=this,d=new ma(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT key FROM "+e.storeName+" WHERE id = ? LIMIT 1",[a+1],function(a,c){var d=c.rows.length?c.rows.item(0).key:null;b(d)},function(a,b){d(b)})})}).catch(d)});return j(d,b),d}function X(a){var b=this,c=new ma(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("SELECT key FROM "+d.storeName,[],function(b,c){for(var d=[],e=0;e=0;c--){var d=localStorage.key(c);0===d.indexOf(a)&&localStorage.removeItem(d)}});return j(c,a),c}function $(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=c.ready().then(function(){var b=c._dbInfo,d=localStorage.getItem(b.keyPrefix+a);return d&&(d=b.serializer.deserialize(d)),d});return j(d,b),d}function _(a,b){var c=this,d=c.ready().then(function(){for(var b=c._dbInfo,d=b.keyPrefix,e=d.length,f=localStorage.length,g=1,h=0;h + + + + Simple localForage example + + + + + + diff --git a/src/drivers/indexeddb.js b/src/drivers/indexeddb.js index 51f936d..7cc54c6 100644 --- a/src/drivers/indexeddb.js +++ b/src/drivers/indexeddb.js @@ -12,6 +12,10 @@ var supportsBlobs; var dbContexts; var toString = Object.prototype.toString; +// Transaction Modes +var READ_ONLY = 'readonly'; +var READ_WRITE = 'readwrite'; + // 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. @@ -44,7 +48,7 @@ function _binStringToArrayBuffer(bin) { // function _checkBlobSupportWithoutCaching(idb) { return new Promise(function(resolve) { - var txn = idb.transaction(DETECT_BLOB_SUPPORT_STORE, 'readwrite'); + var txn = idb.transaction(DETECT_BLOB_SUPPORT_STORE, READ_WRITE); var blob = createBlob(['']); txn.objectStore(DETECT_BLOB_SUPPORT_STORE).put(blob, 'key'); @@ -115,6 +119,19 @@ function _advanceReadiness(dbInfo) { } } +function _rejectReadiness(dbInfo, err) { + var dbContext = dbContexts[dbInfo.name]; + + // Dequeue a deferred operation. + var deferredOperation = dbContext.deferredOperations.pop(); + + // Reject its promise (which is part of the database readiness + // chain of promises). + if (deferredOperation) { + deferredOperation.reject(err); + } +} + function _getConnection(dbInfo, upgradeNeeded) { return new Promise(function(resolve, reject) { @@ -262,6 +279,54 @@ function _fullyReady(callback) { return promise; } +// Try to establish a new db connection to replace the +// current one which is broken (i.e. experiencing +// InvalidStateError while creating a transaction). +function _tryReconnect(dbInfo) { + _deferReadiness(dbInfo); + + var dbContext = dbContexts[dbInfo.name]; + var forages = dbContext.forages; + + for (var i = 0; i < forages.length; i++) { + if (forages[i]._dbInfo.db) { + forages[i]._dbInfo.db.close(); + forages[i]._dbInfo.db = null; + } + } + + return _getConnection(dbInfo, false).then(function(db) { + for (var j = 0; j < forages.length; j++) { + forages[j]._dbInfo.db = db; + } + }).catch(function(err) { + _rejectReadiness(dbInfo, err); + throw err; + }); +} + + +// FF doesn't like Promises (micro-tasks) and IDDB store operations, +// so we have to do it with callbacks +function createTransaction(dbInfo, mode, callback) { + try { + var tx = dbInfo.db.transaction(dbInfo.storeName, mode); + callback(null, tx); + } catch (err) { + if (!dbInfo.db || + err.name === 'InvalidStateError') { + return _tryReconnect(dbInfo).then(function() { + + var tx = dbInfo.db.transaction(dbInfo.storeName, mode); + callback(null, tx); + }); + } + + callback(err); + } +} + + // Open the IndexedDB database (automatically creates one if one didn't // previously exist), using any options set in the config. function _initStorage(options) { @@ -355,6 +420,7 @@ function _initStorage(options) { }); } + function getItem(key, callback) { var self = this; @@ -367,25 +433,33 @@ function getItem(key, callback) { 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; + createTransaction(self._dbInfo, READ_ONLY, function(err, transaction) { + if (err) { + return reject(err); } - if (_isEncodedBlob(value)) { - value = _decodeBlob(value); - } - resolve(value); - }; - req.onerror = function() { - reject(req.error); - }; + try { + var store = transaction.objectStore(self._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 (e) { + reject(e); + } + }); }).catch(reject); }); @@ -399,37 +473,47 @@ function iterate(iterator, callback) { 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 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(); + createTransaction(self._dbInfo, READ_ONLY, function(err, transaction) { + if (err) { + return reject(err); } - }; - req.onerror = function() { - reject(req.error); - }; + try { + var store = transaction.objectStore(self._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++); + + // when the iterator callback retuns any + // (non-`undefined`) value, then we stop + // the iteration immediately + if (result !== void(0)) { + resolve(result); + } else { + cursor.continue(); + } + } else { + resolve(); + } + }; + + req.onerror = function() { + reject(req.error); + }; + } catch (e) { + reject(e); + } + }); }).catch(reject); }); @@ -462,35 +546,44 @@ function setItem(key, value, callback) { } 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; + createTransaction(self._dbInfo, READ_WRITE, function(err, transaction) { + if (err) { + return reject(err); } - resolve(value); - }; - transaction.onabort = transaction.onerror = function() { - var err = req.error ? req.error : req.transaction.error; - reject(err); - }; + try { + var store = transaction.objectStore(self._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 (e) { + reject(e); + } + }); }).catch(reject); }); @@ -510,30 +603,37 @@ function removeItem(key, callback) { 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); + createTransaction(self._dbInfo, READ_WRITE, function(err, transaction) { + if (err) { + return reject(err); + } - // 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(); - }; + try { + var store = transaction.objectStore(self._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); - }; + 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); - }; + // 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 (e) { + reject(e); + } + }); }).catch(reject); }); @@ -546,19 +646,27 @@ function clear(callback) { 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(); + createTransaction(self._dbInfo, READ_WRITE, function(err, transaction) { + if (err) { + return reject(err); + } - transaction.oncomplete = function() { - resolve(); - }; + try { + var store = transaction.objectStore(self._dbInfo.storeName); + var req = store.clear(); - transaction.onabort = transaction.onerror = function() { - var err = req.error ? req.error : req.transaction.error; - reject(err); - }; + transaction.oncomplete = function() { + resolve(); + }; + + transaction.onabort = transaction.onerror = function() { + var err = req.error ? req.error : req.transaction.error; + reject(err); + }; + } catch (e) { + reject(e); + } + }); }).catch(reject); }); @@ -571,18 +679,26 @@ function length(callback) { 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(); + createTransaction(self._dbInfo, READ_ONLY, function(err, transaction) { + if (err) { + return reject(err); + } - req.onsuccess = function() { - resolve(req.result); - }; + try { + var store = transaction.objectStore(self._dbInfo.storeName); + var req = store.count(); - req.onerror = function() { - reject(req.error); - }; + req.onsuccess = function() { + resolve(req.result); + }; + + req.onerror = function() { + reject(req.error); + }; + } catch (e) { + reject(e); + } + }); }).catch(reject); }); @@ -601,41 +717,49 @@ function key(n, callback) { } 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; + createTransaction(self._dbInfo, READ_ONLY, function(err, transaction) { + if (err) { + return reject(err); } - 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); - } - } - }; + try { + var store = transaction.objectStore(self._dbInfo.storeName); + var advanced = false; + var req = store.openCursor(); - req.onerror = function() { - reject(req.error); - }; + 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 (e) { + reject(e); + } + }); }).catch(reject); }); @@ -648,28 +772,35 @@ function keys(callback) { 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; + createTransaction(self._dbInfo, READ_ONLY, function(err, transaction) { + if (err) { + return reject(err); } - keys.push(cursor.key); - cursor.continue(); - }; + try { + var store = transaction.objectStore(self._dbInfo.storeName); + var req = store.openCursor(); + var keys = []; - req.onerror = function() { - reject(req.error); - }; + 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 (e) { + reject(e); + } + }); }).catch(reject); }); diff --git a/test/test.api.js b/test/test.api.js index 71b897e..4132be3 100644 --- a/test/test.api.js +++ b/test/test.api.js @@ -264,6 +264,83 @@ DRIVERS.forEach(function(driverName) { localforage._dbInfo.db.transaction = transaction; }); }); + + describe('recover (reconnect) from IDBDatabase InvalidStateError', function() { + + beforeEach(function(done) { + Promise.all([ + localforage.setItem('key', 'value1'), + localforage.setItem('key1', 'value1'), + localforage.setItem('key2', 'value2'), + localforage.setItem('key3', 'value3') + ]).then(function() { + localforage._dbInfo.db.close(); + done(); + }, function(error) { + done(error || 'error'); + }); + }); + + it('retrieves an item from the storage', function(done) { + localforage.getItem('key').then(function(value) { + expect(value).to.be('value1'); + done(); + }, function(error) { + done(error || 'error'); + }); + }); + + it('retrieves more than one items from the storage', function(done) { + Promise.all([ + localforage.getItem('key1'), + localforage.getItem('key2'), + localforage.getItem('key3') + ]).then(function(values) { + expect(values).to.eql([ + 'value1', + 'value2', + 'value3' + ]); + done(); + }, function(error) { + done(error || 'error'); + }); + }); + + it('stores and retrieves an item from the storage', function(done) { + localforage.setItem('key', 'value1b').then(function() { + return localforage.getItem('key'); + }).then(function(value) { + expect(value).to.be('value1b'); + done(); + }, function(error) { + done(error || 'error'); + }); + }); + + it('stores and retrieves more than one items from the storage', function(done) { + Promise.all([ + localforage.setItem('key1', 'value1b'), + localforage.setItem('key2', 'value2b'), + localforage.setItem('key3', 'value3b') + ]).then(function() { + return Promise.all([ + localforage.getItem('key1'), + localforage.getItem('key2'), + localforage.getItem('key3') + ]); + }).then(function(values) { + expect(values).to.eql([ + 'value1b', + 'value2b', + 'value3b' + ]); + done(); + }, function(error) { + done(error || 'error'); + }); + }); + }); } if (driverName === localforage.WEBSQL) {