| import { |
| guardedConsole, |
| toPromise, |
| hasLocalStorage, |
| uuid, |
| nextTick |
| } from 'pouchdb-utils'; |
| import { |
| isDeleted, |
| isLocalId, |
| traverseRevTree, |
| winningRev as calculateWinningRev, |
| latest as getLatest |
| } from 'pouchdb-merge'; |
| |
| import idbBulkDocs from './bulkDocs'; |
| import idbAllDocs from './allDocs'; |
| import checkBlobSupport from './blobSupport'; |
| import countDocs from './countDocs'; |
| import { |
| MISSING_DOC, |
| REV_CONFLICT, |
| IDB_ERROR, |
| createError |
| } from 'pouchdb-errors'; |
| |
| import { |
| ADAPTER_VERSION, |
| ATTACH_AND_SEQ_STORE, |
| ATTACH_STORE, |
| BY_SEQ_STORE, |
| DETECT_BLOB_SUPPORT_STORE, |
| DOC_STORE, |
| LOCAL_STORE, |
| META_STORE |
| } from './constants'; |
| |
| import { |
| compactRevs, |
| decodeDoc, |
| decodeMetadata, |
| encodeMetadata, |
| idbError, |
| readBlobData, |
| openTransactionSafely |
| } from './utils'; |
| |
| import { enqueueTask } from './taskQueue'; |
| |
| import changesHandler from './changesHandler'; |
| import changes from './changes'; |
| |
| var cachedDBs = new Map(); |
| var blobSupportPromise; |
| var openReqList = new Map(); |
| |
| function IdbPouch(opts, callback) { |
| var api = this; |
| |
| enqueueTask(function (thisCallback) { |
| init(api, opts, thisCallback); |
| }, callback, api.constructor); |
| } |
| |
| function init(api, opts, callback) { |
| |
| var dbName = opts.name; |
| |
| var idb = null; |
| api._meta = null; |
| |
| // called when creating a fresh new database |
| function createSchema(db) { |
| var docStore = db.createObjectStore(DOC_STORE, {keyPath : 'id'}); |
| db.createObjectStore(BY_SEQ_STORE, {autoIncrement: true}) |
| .createIndex('_doc_id_rev', '_doc_id_rev', {unique: true}); |
| db.createObjectStore(ATTACH_STORE, {keyPath: 'digest'}); |
| db.createObjectStore(META_STORE, {keyPath: 'id', autoIncrement: false}); |
| db.createObjectStore(DETECT_BLOB_SUPPORT_STORE); |
| |
| // added in v2 |
| docStore.createIndex('deletedOrLocal', 'deletedOrLocal', {unique : false}); |
| |
| // added in v3 |
| db.createObjectStore(LOCAL_STORE, {keyPath: '_id'}); |
| |
| // added in v4 |
| var attAndSeqStore = db.createObjectStore(ATTACH_AND_SEQ_STORE, |
| {autoIncrement: true}); |
| attAndSeqStore.createIndex('seq', 'seq'); |
| attAndSeqStore.createIndex('digestSeq', 'digestSeq', {unique: true}); |
| } |
| |
| // migration to version 2 |
| // unfortunately "deletedOrLocal" is a misnomer now that we no longer |
| // store local docs in the main doc-store, but whaddyagonnado |
| function addDeletedOrLocalIndex(txn, callback) { |
| var docStore = txn.objectStore(DOC_STORE); |
| docStore.createIndex('deletedOrLocal', 'deletedOrLocal', {unique : false}); |
| |
| docStore.openCursor().onsuccess = function (event) { |
| var cursor = event.target.result; |
| if (cursor) { |
| var metadata = cursor.value; |
| var deleted = isDeleted(metadata); |
| metadata.deletedOrLocal = deleted ? "1" : "0"; |
| docStore.put(metadata); |
| cursor.continue(); |
| } else { |
| callback(); |
| } |
| }; |
| } |
| |
| // migration to version 3 (part 1) |
| function createLocalStoreSchema(db) { |
| db.createObjectStore(LOCAL_STORE, {keyPath: '_id'}) |
| .createIndex('_doc_id_rev', '_doc_id_rev', {unique: true}); |
| } |
| |
| // migration to version 3 (part 2) |
| function migrateLocalStore(txn, cb) { |
| var localStore = txn.objectStore(LOCAL_STORE); |
| var docStore = txn.objectStore(DOC_STORE); |
| var seqStore = txn.objectStore(BY_SEQ_STORE); |
| |
| var cursor = docStore.openCursor(); |
| cursor.onsuccess = function (event) { |
| var cursor = event.target.result; |
| if (cursor) { |
| var metadata = cursor.value; |
| var docId = metadata.id; |
| var local = isLocalId(docId); |
| var rev = calculateWinningRev(metadata); |
| if (local) { |
| var docIdRev = docId + "::" + rev; |
| // remove all seq entries |
| // associated with this docId |
| var start = docId + "::"; |
| var end = docId + "::~"; |
| var index = seqStore.index('_doc_id_rev'); |
| var range = IDBKeyRange.bound(start, end, false, false); |
| var seqCursor = index.openCursor(range); |
| seqCursor.onsuccess = function (e) { |
| seqCursor = e.target.result; |
| if (!seqCursor) { |
| // done |
| docStore.delete(cursor.primaryKey); |
| cursor.continue(); |
| } else { |
| var data = seqCursor.value; |
| if (data._doc_id_rev === docIdRev) { |
| localStore.put(data); |
| } |
| seqStore.delete(seqCursor.primaryKey); |
| seqCursor.continue(); |
| } |
| }; |
| } else { |
| cursor.continue(); |
| } |
| } else if (cb) { |
| cb(); |
| } |
| }; |
| } |
| |
| // migration to version 4 (part 1) |
| function addAttachAndSeqStore(db) { |
| var attAndSeqStore = db.createObjectStore(ATTACH_AND_SEQ_STORE, |
| {autoIncrement: true}); |
| attAndSeqStore.createIndex('seq', 'seq'); |
| attAndSeqStore.createIndex('digestSeq', 'digestSeq', {unique: true}); |
| } |
| |
| // migration to version 4 (part 2) |
| function migrateAttsAndSeqs(txn, callback) { |
| var seqStore = txn.objectStore(BY_SEQ_STORE); |
| var attStore = txn.objectStore(ATTACH_STORE); |
| var attAndSeqStore = txn.objectStore(ATTACH_AND_SEQ_STORE); |
| |
| // need to actually populate the table. this is the expensive part, |
| // so as an optimization, check first that this database even |
| // contains attachments |
| var req = attStore.count(); |
| req.onsuccess = function (e) { |
| var count = e.target.result; |
| if (!count) { |
| return callback(); // done |
| } |
| |
| seqStore.openCursor().onsuccess = function (e) { |
| var cursor = e.target.result; |
| if (!cursor) { |
| return callback(); // done |
| } |
| var doc = cursor.value; |
| var seq = cursor.primaryKey; |
| var atts = Object.keys(doc._attachments || {}); |
| var digestMap = {}; |
| for (var j = 0; j < atts.length; j++) { |
| var att = doc._attachments[atts[j]]; |
| digestMap[att.digest] = true; // uniq digests, just in case |
| } |
| var digests = Object.keys(digestMap); |
| for (j = 0; j < digests.length; j++) { |
| var digest = digests[j]; |
| attAndSeqStore.put({ |
| seq: seq, |
| digestSeq: digest + '::' + seq |
| }); |
| } |
| cursor.continue(); |
| }; |
| }; |
| } |
| |
| // migration to version 5 |
| // Instead of relying on on-the-fly migration of metadata, |
| // this brings the doc-store to its modern form: |
| // - metadata.winningrev |
| // - metadata.seq |
| // - stringify the metadata when storing it |
| function migrateMetadata(txn) { |
| |
| function decodeMetadataCompat(storedObject) { |
| if (!storedObject.data) { |
| // old format, when we didn't store it stringified |
| storedObject.deleted = storedObject.deletedOrLocal === '1'; |
| return storedObject; |
| } |
| return decodeMetadata(storedObject); |
| } |
| |
| // ensure that every metadata has a winningRev and seq, |
| // which was previously created on-the-fly but better to migrate |
| var bySeqStore = txn.objectStore(BY_SEQ_STORE); |
| var docStore = txn.objectStore(DOC_STORE); |
| var cursor = docStore.openCursor(); |
| cursor.onsuccess = function (e) { |
| var cursor = e.target.result; |
| if (!cursor) { |
| return; // done |
| } |
| var metadata = decodeMetadataCompat(cursor.value); |
| |
| metadata.winningRev = metadata.winningRev || |
| calculateWinningRev(metadata); |
| |
| function fetchMetadataSeq() { |
| // metadata.seq was added post-3.2.0, so if it's missing, |
| // we need to fetch it manually |
| var start = metadata.id + '::'; |
| var end = metadata.id + '::\uffff'; |
| var req = bySeqStore.index('_doc_id_rev').openCursor( |
| IDBKeyRange.bound(start, end)); |
| |
| var metadataSeq = 0; |
| req.onsuccess = function (e) { |
| var cursor = e.target.result; |
| if (!cursor) { |
| metadata.seq = metadataSeq; |
| return onGetMetadataSeq(); |
| } |
| var seq = cursor.primaryKey; |
| if (seq > metadataSeq) { |
| metadataSeq = seq; |
| } |
| cursor.continue(); |
| }; |
| } |
| |
| function onGetMetadataSeq() { |
| var metadataToStore = encodeMetadata(metadata, |
| metadata.winningRev, metadata.deleted); |
| |
| var req = docStore.put(metadataToStore); |
| req.onsuccess = function () { |
| cursor.continue(); |
| }; |
| } |
| |
| if (metadata.seq) { |
| return onGetMetadataSeq(); |
| } |
| |
| fetchMetadataSeq(); |
| }; |
| |
| } |
| |
| api.type = function () { |
| return 'idb'; |
| }; |
| |
| api._id = toPromise(function (callback) { |
| callback(null, api._meta.instanceId); |
| }); |
| |
| api._bulkDocs = function idb_bulkDocs(req, reqOpts, callback) { |
| idbBulkDocs(opts, req, reqOpts, api, idb, callback); |
| }; |
| |
| // First we look up the metadata in the ids database, then we fetch the |
| // current revision(s) from the by sequence store |
| api._get = function idb_get(id, opts, callback) { |
| var doc; |
| var metadata; |
| var err; |
| var txn = opts.ctx; |
| if (!txn) { |
| var txnResult = openTransactionSafely(idb, |
| [DOC_STORE, BY_SEQ_STORE, ATTACH_STORE], 'readonly'); |
| if (txnResult.error) { |
| return callback(txnResult.error); |
| } |
| txn = txnResult.txn; |
| } |
| |
| function finish() { |
| callback(err, {doc: doc, metadata: metadata, ctx: txn}); |
| } |
| |
| txn.objectStore(DOC_STORE).get(id).onsuccess = function (e) { |
| metadata = decodeMetadata(e.target.result); |
| // we can determine the result here if: |
| // 1. there is no such document |
| // 2. the document is deleted and we don't ask about specific rev |
| // When we ask with opts.rev we expect the answer to be either |
| // doc (possibly with _deleted=true) or missing error |
| if (!metadata) { |
| err = createError(MISSING_DOC, 'missing'); |
| return finish(); |
| } |
| |
| var rev; |
| if(!opts.rev) { |
| rev = metadata.winningRev; |
| var deleted = isDeleted(metadata); |
| if (deleted) { |
| err = createError(MISSING_DOC, "deleted"); |
| return finish(); |
| } |
| } else { |
| rev = opts.latest ? getLatest(opts.rev, metadata) : opts.rev; |
| } |
| |
| var objectStore = txn.objectStore(BY_SEQ_STORE); |
| var key = metadata.id + '::' + rev; |
| |
| objectStore.index('_doc_id_rev').get(key).onsuccess = function (e) { |
| doc = e.target.result; |
| if (doc) { |
| doc = decodeDoc(doc); |
| } |
| if (!doc) { |
| err = createError(MISSING_DOC, 'missing'); |
| return finish(); |
| } |
| finish(); |
| }; |
| }; |
| }; |
| |
| api._getAttachment = function (docId, attachId, attachment, opts, callback) { |
| var txn; |
| if (opts.ctx) { |
| txn = opts.ctx; |
| } else { |
| var txnResult = openTransactionSafely(idb, |
| [DOC_STORE, BY_SEQ_STORE, ATTACH_STORE], 'readonly'); |
| if (txnResult.error) { |
| return callback(txnResult.error); |
| } |
| txn = txnResult.txn; |
| } |
| var digest = attachment.digest; |
| var type = attachment.content_type; |
| |
| txn.objectStore(ATTACH_STORE).get(digest).onsuccess = function (e) { |
| var body = e.target.result.body; |
| readBlobData(body, type, opts.binary, function (blobData) { |
| callback(null, blobData); |
| }); |
| }; |
| }; |
| |
| api._info = function idb_info(callback) { |
| var updateSeq; |
| var docCount; |
| |
| var txnResult = openTransactionSafely(idb, [META_STORE, BY_SEQ_STORE], 'readonly'); |
| if (txnResult.error) { |
| return callback(txnResult.error); |
| } |
| var txn = txnResult.txn; |
| txn.objectStore(META_STORE).get(META_STORE).onsuccess = function (e) { |
| docCount = e.target.result.docCount; |
| }; |
| txn.objectStore(BY_SEQ_STORE).openCursor(null, 'prev').onsuccess = function (e) { |
| var cursor = e.target.result; |
| updateSeq = cursor ? cursor.key : 0; |
| }; |
| |
| txn.oncomplete = function () { |
| callback(null, { |
| doc_count: docCount, |
| update_seq: updateSeq, |
| // for debugging |
| idb_attachment_format: (api._meta.blobSupport ? 'binary' : 'base64') |
| }); |
| }; |
| }; |
| |
| api._allDocs = function idb_allDocs(opts, callback) { |
| idbAllDocs(opts, idb, callback); |
| }; |
| |
| api._changes = function idbChanges(opts) { |
| changes(opts, api, dbName, idb); |
| }; |
| |
| api._close = function (callback) { |
| // https://developer.mozilla.org/en-US/docs/IndexedDB/IDBDatabase#close |
| // "Returns immediately and closes the connection in a separate thread..." |
| idb.close(); |
| cachedDBs.delete(dbName); |
| callback(); |
| }; |
| |
| api._getRevisionTree = function (docId, callback) { |
| var txnResult = openTransactionSafely(idb, [DOC_STORE], 'readonly'); |
| if (txnResult.error) { |
| return callback(txnResult.error); |
| } |
| var txn = txnResult.txn; |
| var req = txn.objectStore(DOC_STORE).get(docId); |
| req.onsuccess = function (event) { |
| var doc = decodeMetadata(event.target.result); |
| if (!doc) { |
| callback(createError(MISSING_DOC)); |
| } else { |
| callback(null, doc.rev_tree); |
| } |
| }; |
| }; |
| |
| // This function removes revisions of document docId |
| // which are listed in revs and sets this document |
| // revision to to rev_tree |
| api._doCompaction = function (docId, revs, callback) { |
| var stores = [ |
| DOC_STORE, |
| BY_SEQ_STORE, |
| ATTACH_STORE, |
| ATTACH_AND_SEQ_STORE |
| ]; |
| var txnResult = openTransactionSafely(idb, stores, 'readwrite'); |
| if (txnResult.error) { |
| return callback(txnResult.error); |
| } |
| var txn = txnResult.txn; |
| |
| var docStore = txn.objectStore(DOC_STORE); |
| |
| docStore.get(docId).onsuccess = function (event) { |
| var metadata = decodeMetadata(event.target.result); |
| traverseRevTree(metadata.rev_tree, function (isLeaf, pos, |
| revHash, ctx, opts) { |
| var rev = pos + '-' + revHash; |
| if (revs.indexOf(rev) !== -1) { |
| opts.status = 'missing'; |
| } |
| }); |
| compactRevs(revs, docId, txn); |
| var winningRev = metadata.winningRev; |
| var deleted = metadata.deleted; |
| txn.objectStore(DOC_STORE).put( |
| encodeMetadata(metadata, winningRev, deleted)); |
| }; |
| txn.onabort = idbError(callback); |
| txn.oncomplete = function () { |
| callback(); |
| }; |
| }; |
| |
| |
| api._getLocal = function (id, callback) { |
| var txnResult = openTransactionSafely(idb, [LOCAL_STORE], 'readonly'); |
| if (txnResult.error) { |
| return callback(txnResult.error); |
| } |
| var tx = txnResult.txn; |
| var req = tx.objectStore(LOCAL_STORE).get(id); |
| |
| req.onerror = idbError(callback); |
| req.onsuccess = function (e) { |
| var doc = e.target.result; |
| if (!doc) { |
| callback(createError(MISSING_DOC)); |
| } else { |
| delete doc['_doc_id_rev']; // for backwards compat |
| callback(null, doc); |
| } |
| }; |
| }; |
| |
| api._putLocal = function (doc, opts, callback) { |
| if (typeof opts === 'function') { |
| callback = opts; |
| opts = {}; |
| } |
| delete doc._revisions; // ignore this, trust the rev |
| var oldRev = doc._rev; |
| var id = doc._id; |
| if (!oldRev) { |
| doc._rev = '0-1'; |
| } else { |
| doc._rev = '0-' + (parseInt(oldRev.split('-')[1], 10) + 1); |
| } |
| |
| var tx = opts.ctx; |
| var ret; |
| if (!tx) { |
| var txnResult = openTransactionSafely(idb, [LOCAL_STORE], 'readwrite'); |
| if (txnResult.error) { |
| return callback(txnResult.error); |
| } |
| tx = txnResult.txn; |
| tx.onerror = idbError(callback); |
| tx.oncomplete = function () { |
| if (ret) { |
| callback(null, ret); |
| } |
| }; |
| } |
| |
| var oStore = tx.objectStore(LOCAL_STORE); |
| var req; |
| if (oldRev) { |
| req = oStore.get(id); |
| req.onsuccess = function (e) { |
| var oldDoc = e.target.result; |
| if (!oldDoc || oldDoc._rev !== oldRev) { |
| callback(createError(REV_CONFLICT)); |
| } else { // update |
| var req = oStore.put(doc); |
| req.onsuccess = function () { |
| ret = {ok: true, id: doc._id, rev: doc._rev}; |
| if (opts.ctx) { // return immediately |
| callback(null, ret); |
| } |
| }; |
| } |
| }; |
| } else { // new doc |
| req = oStore.add(doc); |
| req.onerror = function (e) { |
| // constraint error, already exists |
| callback(createError(REV_CONFLICT)); |
| e.preventDefault(); // avoid transaction abort |
| e.stopPropagation(); // avoid transaction onerror |
| }; |
| req.onsuccess = function () { |
| ret = {ok: true, id: doc._id, rev: doc._rev}; |
| if (opts.ctx) { // return immediately |
| callback(null, ret); |
| } |
| }; |
| } |
| }; |
| |
| api._removeLocal = function (doc, opts, callback) { |
| if (typeof opts === 'function') { |
| callback = opts; |
| opts = {}; |
| } |
| var tx = opts.ctx; |
| if (!tx) { |
| var txnResult = openTransactionSafely(idb, [LOCAL_STORE], 'readwrite'); |
| if (txnResult.error) { |
| return callback(txnResult.error); |
| } |
| tx = txnResult.txn; |
| tx.oncomplete = function () { |
| if (ret) { |
| callback(null, ret); |
| } |
| }; |
| } |
| var ret; |
| var id = doc._id; |
| var oStore = tx.objectStore(LOCAL_STORE); |
| var req = oStore.get(id); |
| |
| req.onerror = idbError(callback); |
| req.onsuccess = function (e) { |
| var oldDoc = e.target.result; |
| if (!oldDoc || oldDoc._rev !== doc._rev) { |
| callback(createError(MISSING_DOC)); |
| } else { |
| oStore.delete(id); |
| ret = {ok: true, id: id, rev: '0-0'}; |
| if (opts.ctx) { // return immediately |
| callback(null, ret); |
| } |
| } |
| }; |
| }; |
| |
| api._destroy = function (opts, callback) { |
| changesHandler.removeAllListeners(dbName); |
| |
| //Close open request for "dbName" database to fix ie delay. |
| var openReq = openReqList.get(dbName); |
| if (openReq && openReq.result) { |
| openReq.result.close(); |
| cachedDBs.delete(dbName); |
| } |
| var req = indexedDB.deleteDatabase(dbName); |
| |
| req.onsuccess = function () { |
| //Remove open request from the list. |
| openReqList.delete(dbName); |
| if (hasLocalStorage() && (dbName in localStorage)) { |
| delete localStorage[dbName]; |
| } |
| callback(null, { 'ok': true }); |
| }; |
| |
| req.onerror = idbError(callback); |
| }; |
| |
| var cached = cachedDBs.get(dbName); |
| |
| if (cached) { |
| idb = cached.idb; |
| api._meta = cached.global; |
| return nextTick(function () { |
| callback(null, api); |
| }); |
| } |
| |
| var req; |
| if (opts.storage) { |
| req = tryStorageOption(dbName, opts.storage); |
| } else { |
| req = indexedDB.open(dbName, ADAPTER_VERSION); |
| } |
| |
| openReqList.set(dbName, req); |
| |
| req.onupgradeneeded = function (e) { |
| var db = e.target.result; |
| if (e.oldVersion < 1) { |
| return createSchema(db); // new db, initial schema |
| } |
| // do migrations |
| |
| var txn = e.currentTarget.transaction; |
| // these migrations have to be done in this function, before |
| // control is returned to the event loop, because IndexedDB |
| |
| if (e.oldVersion < 3) { |
| createLocalStoreSchema(db); // v2 -> v3 |
| } |
| if (e.oldVersion < 4) { |
| addAttachAndSeqStore(db); // v3 -> v4 |
| } |
| |
| var migrations = [ |
| addDeletedOrLocalIndex, // v1 -> v2 |
| migrateLocalStore, // v2 -> v3 |
| migrateAttsAndSeqs, // v3 -> v4 |
| migrateMetadata // v4 -> v5 |
| ]; |
| |
| var i = e.oldVersion; |
| |
| function next() { |
| var migration = migrations[i - 1]; |
| i++; |
| if (migration) { |
| migration(txn, next); |
| } |
| } |
| |
| next(); |
| }; |
| |
| req.onsuccess = function (e) { |
| |
| idb = e.target.result; |
| |
| idb.onversionchange = function () { |
| idb.close(); |
| cachedDBs.delete(dbName); |
| }; |
| |
| idb.onabort = function (e) { |
| guardedConsole('error', 'Database has a global failure', e.target.error); |
| idb.close(); |
| cachedDBs.delete(dbName); |
| }; |
| |
| // Do a few setup operations (in parallel as much as possible): |
| // 1. Fetch meta doc |
| // 2. Check blob support |
| // 3. Calculate docCount |
| // 4. Generate an instanceId if necessary |
| // 5. Store docCount and instanceId on meta doc |
| |
| var txn = idb.transaction([ |
| META_STORE, |
| DETECT_BLOB_SUPPORT_STORE, |
| DOC_STORE |
| ], 'readwrite'); |
| |
| var storedMetaDoc = false; |
| var metaDoc; |
| var docCount; |
| var blobSupport; |
| var instanceId; |
| |
| function completeSetup() { |
| if (typeof blobSupport === 'undefined' || !storedMetaDoc) { |
| return; |
| } |
| api._meta = { |
| name: dbName, |
| instanceId: instanceId, |
| blobSupport: blobSupport |
| }; |
| |
| cachedDBs.set(dbName, { |
| idb: idb, |
| global: api._meta |
| }); |
| callback(null, api); |
| } |
| |
| function storeMetaDocIfReady() { |
| if (typeof docCount === 'undefined' || typeof metaDoc === 'undefined') { |
| return; |
| } |
| var instanceKey = dbName + '_id'; |
| if (instanceKey in metaDoc) { |
| instanceId = metaDoc[instanceKey]; |
| } else { |
| metaDoc[instanceKey] = instanceId = uuid(); |
| } |
| metaDoc.docCount = docCount; |
| txn.objectStore(META_STORE).put(metaDoc); |
| } |
| |
| // |
| // fetch or generate the instanceId |
| // |
| txn.objectStore(META_STORE).get(META_STORE).onsuccess = function (e) { |
| metaDoc = e.target.result || { id: META_STORE }; |
| storeMetaDocIfReady(); |
| }; |
| |
| // |
| // countDocs |
| // |
| countDocs(txn, function (count) { |
| docCount = count; |
| storeMetaDocIfReady(); |
| }); |
| |
| // |
| // check blob support |
| // |
| if (!blobSupportPromise) { |
| // make sure blob support is only checked once |
| blobSupportPromise = checkBlobSupport(txn); |
| } |
| |
| blobSupportPromise.then(function (val) { |
| blobSupport = val; |
| completeSetup(); |
| }); |
| |
| // only when the metadata put transaction has completed, |
| // consider the setup done |
| txn.oncomplete = function () { |
| storedMetaDoc = true; |
| completeSetup(); |
| }; |
| }; |
| |
| req.onerror = function () { |
| var msg = 'Failed to open indexedDB, are you in private browsing mode?'; |
| guardedConsole('error', msg); |
| callback(createError(IDB_ERROR, msg)); |
| }; |
| } |
| |
| IdbPouch.valid = function () { |
| // Issue #2533, we finally gave up on doing bug |
| // detection instead of browser sniffing. Safari brought us |
| // to our knees. |
| var isSafari = typeof openDatabase !== 'undefined' && |
| /(Safari|iPhone|iPad|iPod)/.test(navigator.userAgent) && |
| !/Chrome/.test(navigator.userAgent) && |
| !/BlackBerry/.test(navigator.platform); |
| |
| // some outdated implementations of IDB that appear on Samsung |
| // and HTC Android devices <4.4 are missing IDBKeyRange |
| return !isSafari && typeof indexedDB !== 'undefined' && |
| typeof IDBKeyRange !== 'undefined'; |
| }; |
| |
| function tryStorageOption(dbName, storage) { |
| try { // option only available in Firefox 26+ |
| return indexedDB.open(dbName, { |
| version: ADAPTER_VERSION, |
| storage: storage |
| }); |
| } catch(err) { |
| return indexedDB.open(dbName, ADAPTER_VERSION); |
| } |
| } |
| |
| export default function (PouchDB) { |
| PouchDB.adapter('idb', IdbPouch, true); |
| } |