From 36be62a85c3cc47c15c9a59f20cdfcd7d0a72ad9 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Wed, 5 Aug 2020 15:15:21 -0700 Subject: [PATCH] Re-open IndexedDB if closed (#3535) --- .changeset/quick-drinks-cheat.md | 5 + .../src/local/indexeddb_persistence.ts | 25 +- packages/firestore/src/local/simple_db.ts | 217 ++++++++++-------- .../unit/local/encoded_resource_path.test.ts | 15 +- .../unit/local/indexeddb_persistence.test.ts | 154 +++++-------- .../test/unit/local/simple_db.test.ts | 13 +- .../test/unit/specs/spec_test_runner.ts | 2 +- 7 files changed, 208 insertions(+), 223 deletions(-) create mode 100644 .changeset/quick-drinks-cheat.md diff --git a/.changeset/quick-drinks-cheat.md b/.changeset/quick-drinks-cheat.md new file mode 100644 index 00000000000..3ea90047f76 --- /dev/null +++ b/.changeset/quick-drinks-cheat.md @@ -0,0 +1,5 @@ +--- +"@firebase/firestore": patch +--- + +The SDK no longer crashes with the error "The database connection is closing". Instead, the individual operations that cause this error may be rejected. diff --git a/packages/firestore/src/local/indexeddb_persistence.ts b/packages/firestore/src/local/indexeddb_persistence.ts index 65d8a80b921..bf91642fdf1 100644 --- a/packages/firestore/src/local/indexeddb_persistence.ts +++ b/packages/firestore/src/local/indexeddb_persistence.ts @@ -191,10 +191,7 @@ export class IndexedDbPersistence implements Persistence { } } - // Technically `simpleDb` should be `| undefined` because it is - // initialized asynchronously by start(), but that would be more misleading - // than useful. - private simpleDb!: SimpleDb; + private simpleDb: SimpleDb; private listenSequence: ListenSequence | null = null; @@ -259,6 +256,11 @@ export class IndexedDbPersistence implements Persistence { this.referenceDelegate = new IndexedDbLruDelegate(this, lruParams); this.dbName = persistenceKey + MAIN_DATABASE; this.serializer = new LocalSerializer(serializer); + this.simpleDb = new SimpleDb( + this.dbName, + SCHEMA_VERSION, + new SchemaConverter(this.serializer) + ); this.targetCache = new IndexedDbTargetCache( this.referenceDelegate, this.serializer @@ -292,17 +294,10 @@ export class IndexedDbPersistence implements Persistence { debugAssert(!this.started, 'IndexedDbPersistence double-started!'); debugAssert(this.window !== null, "Expected 'window' to be defined"); - return SimpleDb.openOrCreate( - this.dbName, - SCHEMA_VERSION, - new SchemaConverter(this.serializer) - ) - .then(db => { - this.simpleDb = db; - // NOTE: This is expected to fail sometimes (in the case of another tab already - // having the persistence lock), so it's the first thing we should do. - return this.updateClientMetadataAndTryBecomePrimary(); - }) + // NOTE: This is expected to fail sometimes (in the case of another tab + // already having the persistence lock), so it's the first thing we should + // do. + return this.updateClientMetadataAndTryBecomePrimary() .then(() => { if (!this.isPrimary && !this.allowTabSynchronization) { // Fail `start()` if `synchronizeTabs` is disabled and we cannot diff --git a/packages/firestore/src/local/simple_db.ts b/packages/firestore/src/local/simple_db.ts index 09cc8705863..9420f2a8f0f 100644 --- a/packages/firestore/src/local/simple_db.ts +++ b/packages/firestore/src/local/simple_db.ts @@ -20,7 +20,6 @@ import { debugAssert } from '../util/assert'; import { Code, FirestoreError } from '../util/error'; import { logDebug, logError } from '../util/log'; import { Deferred } from '../util/promise'; -import { SCHEMA_VERSION } from './indexeddb_schema'; import { PersistencePromise } from './persistence_promise'; // References to `window` are guarded by SimpleDb.isAvailable() @@ -54,88 +53,8 @@ export interface SimpleDbSchemaConverter { * See PersistencePromise for more details. */ export class SimpleDb { - /** - * Opens the specified database, creating or upgrading it if necessary. - * - * Note that `version` must not be a downgrade. IndexedDB does not support downgrading the schema - * version. We currently do not support any way to do versioning outside of IndexedDB's versioning - * mechanism, as only version-upgrade transactions are allowed to do things like create - * objectstores. - */ - static openOrCreate( - name: string, - version: number, - schemaConverter: SimpleDbSchemaConverter - ): Promise { - debugAssert( - SimpleDb.isAvailable(), - 'IndexedDB not supported in current environment.' - ); - logDebug(LOG_TAG, 'Opening database:', name); - return new PersistencePromise((resolve, reject) => { - // TODO(mikelehen): Investigate browser compatibility. - // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB - // suggests IE9 and older WebKit browsers handle upgrade - // differently. They expect setVersion, as described here: - // https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeRequest/setVersion - const request = indexedDB.open(name, version); - - request.onsuccess = (event: Event) => { - const db = (event.target as IDBOpenDBRequest).result; - resolve(new SimpleDb(db)); - }; - - request.onblocked = () => { - reject( - new FirestoreError( - Code.FAILED_PRECONDITION, - 'Cannot upgrade IndexedDB schema while another tab is open. ' + - 'Close all tabs that access Firestore and reload this page to proceed.' - ) - ); - }; - - request.onerror = (event: Event) => { - const error: DOMException = (event.target as IDBOpenDBRequest).error!; - if (error.name === 'VersionError') { - reject( - new FirestoreError( - Code.FAILED_PRECONDITION, - 'A newer version of the Firestore SDK was previously used and so the persisted ' + - 'data is not compatible with the version of the SDK you are now using. The SDK ' + - 'will operate with persistence disabled. If you need persistence, please ' + - 're-upgrade to a newer version of the SDK or else clear the persisted IndexedDB ' + - 'data for your app to start fresh.' - ) - ); - } else { - reject(error); - } - }; - - request.onupgradeneeded = (event: IDBVersionChangeEvent) => { - logDebug( - LOG_TAG, - 'Database "' + name + '" requires upgrade from version:', - event.oldVersion - ); - const db = (event.target as IDBOpenDBRequest).result; - schemaConverter - .createOrUpgrade( - db, - request.transaction!, - event.oldVersion, - SCHEMA_VERSION - ) - .next(() => { - logDebug( - LOG_TAG, - 'Database upgrade to version ' + SCHEMA_VERSION + ' complete' - ); - }); - }; - }).toPromise(); - } + private db?: IDBDatabase; + private versionchangelistener?: (event: IDBVersionChangeEvent) => void; /** Deletes the specified database. */ static delete(name: string): Promise { @@ -233,7 +152,25 @@ export class SimpleDb { return Number(version); } - constructor(private db: IDBDatabase) { + /* + * Creates a new SimpleDb wrapper for IndexedDb database `name`. + * + * Note that `version` must not be a downgrade. IndexedDB does not support + * downgrading the schema version. We currently do not support any way to do + * versioning outside of IndexedDB's versioning mechanism, as only + * version-upgrade transactions are allowed to do things like create + * objectstores. + */ + constructor( + private readonly name: string, + private readonly version: number, + private readonly schemaConverter: SimpleDbSchemaConverter + ) { + debugAssert( + SimpleDb.isAvailable(), + 'IndexedDB not supported in current environment.' + ); + const iOSVersion = SimpleDb.getIOSVersion(getUA()); // NOTE: According to https://bugs.webkit.org/show_bug.cgi?id=197050, the // bug we're checking for should exist in iOS >= 12.2 and < 13, but for @@ -249,12 +186,91 @@ export class SimpleDb { } } + /** + * Opens the specified database, creating or upgrading it if necessary. + */ + async ensureDb(): Promise { + if (!this.db) { + logDebug(LOG_TAG, 'Opening database:', this.name); + this.db = await new Promise((resolve, reject) => { + // TODO(mikelehen): Investigate browser compatibility. + // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB + // suggests IE9 and older WebKit browsers handle upgrade + // differently. They expect setVersion, as described here: + // https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeRequest/setVersion + const request = indexedDB.open(this.name, this.version); + + request.onsuccess = (event: Event) => { + const db = (event.target as IDBOpenDBRequest).result; + resolve(db); + }; + + request.onblocked = () => { + reject( + new IndexedDbTransactionError( + 'Cannot upgrade IndexedDB schema while another tab is open. ' + + 'Close all tabs that access Firestore and reload this page to proceed.' + ) + ); + }; + + request.onerror = (event: Event) => { + const error: DOMException = (event.target as IDBOpenDBRequest).error!; + if (error.name === 'VersionError') { + reject( + new FirestoreError( + Code.FAILED_PRECONDITION, + 'A newer version of the Firestore SDK was previously used and so the persisted ' + + 'data is not compatible with the version of the SDK you are now using. The SDK ' + + 'will operate with persistence disabled. If you need persistence, please ' + + 're-upgrade to a newer version of the SDK or else clear the persisted IndexedDB ' + + 'data for your app to start fresh.' + ) + ); + } else { + reject(new IndexedDbTransactionError(error)); + } + }; + + request.onupgradeneeded = (event: IDBVersionChangeEvent) => { + logDebug( + LOG_TAG, + 'Database "' + this.name + '" requires upgrade from version:', + event.oldVersion + ); + const db = (event.target as IDBOpenDBRequest).result; + this.schemaConverter + .createOrUpgrade( + db, + request.transaction!, + event.oldVersion, + this.version + ) + .next(() => { + logDebug( + LOG_TAG, + 'Database upgrade to version ' + this.version + ' complete' + ); + }); + }; + }); + } + + if (this.versionchangelistener) { + this.db.onversionchange = event => this.versionchangelistener!(event); + } + return this.db; + } + setVersionChangeListener( versionChangeListener: (event: IDBVersionChangeEvent) => void ): void { - this.db.onversionchange = (event: IDBVersionChangeEvent) => { - return versionChangeListener(event); - }; + this.versionchangelistener = versionChangeListener; + if (this.db) { + this.db.onversionchange = (event: IDBVersionChangeEvent) => { + return versionChangeListener(event); + }; + } } async runTransaction( @@ -268,12 +284,14 @@ export class SimpleDb { while (true) { ++attemptNumber; - const transaction = SimpleDbTransaction.open( - this.db, - readonly ? 'readonly' : 'readwrite', - objectStores - ); try { + this.db = await this.ensureDb(); + + const transaction = SimpleDbTransaction.open( + this.db, + readonly ? 'readonly' : 'readwrite', + objectStores + ); const transactionFnResult = transactionFn(transaction) .catch(error => { // Abort the transaction if there was an error. @@ -312,6 +330,8 @@ export class SimpleDb { retryable ); + this.close(); + if (!retryable) { return Promise.reject(error); } @@ -320,7 +340,10 @@ export class SimpleDb { } close(): void { - this.db.close(); + if (this.db) { + this.db.close(); + } + this.db = undefined; } } @@ -400,7 +423,7 @@ export interface IterateOptions { export class IndexedDbTransactionError extends FirestoreError { name = 'IndexedDbTransactionError'; - constructor(cause: Error) { + constructor(cause: Error | string) { super(Code.UNAVAILABLE, 'IndexedDB transaction failed: ' + cause); } } @@ -429,7 +452,11 @@ export class SimpleDbTransaction { mode: IDBTransactionMode, objectStoreNames: string[] ): SimpleDbTransaction { - return new SimpleDbTransaction(db.transaction(objectStoreNames, mode)); + try { + return new SimpleDbTransaction(db.transaction(objectStoreNames, mode)); + } catch (e) { + throw new IndexedDbTransactionError(e); + } } constructor(private readonly transaction: IDBTransaction) { diff --git a/packages/firestore/test/unit/local/encoded_resource_path.test.ts b/packages/firestore/test/unit/local/encoded_resource_path.test.ts index 505410b54ec..8d259973cba 100644 --- a/packages/firestore/test/unit/local/encoded_resource_path.test.ts +++ b/packages/firestore/test/unit/local/encoded_resource_path.test.ts @@ -53,18 +53,9 @@ describe('EncodedResourcePath', () => { const dbName = 'resource-path-tests'; - beforeEach(() => { - return SimpleDb.delete(dbName) - .then(() => { - return SimpleDb.openOrCreate( - dbName, - 1, - new EncodedResourcePathSchemaConverter() - ); - }) - .then(simpleDb => { - db = simpleDb; - }); + beforeEach(async () => { + await SimpleDb.delete(dbName); + db = new SimpleDb(dbName, 1, new EncodedResourcePathSchemaConverter()); }); afterEach(() => { diff --git a/packages/firestore/test/unit/local/indexeddb_persistence.test.ts b/packages/firestore/test/unit/local/indexeddb_persistence.test.ts index 9cc7c0f2e0b..4d67954a57c 100644 --- a/packages/firestore/test/unit/local/indexeddb_persistence.test.ts +++ b/packages/firestore/test/unit/local/indexeddb_persistence.test.ts @@ -86,37 +86,19 @@ import { getWindow } from '../../../src/platform/dom'; use(chaiAsPromised); /* eslint-disable no-restricted-globals */ -function withDb( +async function withDb( schemaVersion: number, - fn: (db: IDBDatabase) => Promise + fn: (db: SimpleDb, version: number, objectStores: string[]) => Promise ): Promise { const schemaConverter = new SchemaConverter(TEST_SERIALIZER); - - return new Promise((resolve, reject) => { - const request = window.indexedDB.open( - INDEXEDDB_TEST_DATABASE_NAME, - schemaVersion - ); - request.onupgradeneeded = (event: IDBVersionChangeEvent) => { - const db = (event.target as IDBOpenDBRequest).result; - schemaConverter.createOrUpgrade( - db, - request.transaction!, - event.oldVersion, - schemaVersion - ); - }; - request.onsuccess = (event: Event) => { - resolve((event.target as IDBOpenDBRequest).result); - }; - request.onerror = (event: Event) => { - reject((event.target as IDBOpenDBRequest).error); - }; - }) - .then(db => fn(db).then(() => db)) - .then(db => { - db.close(); - }); + const simpleDb = new SimpleDb( + INDEXEDDB_TEST_DATABASE_NAME, + schemaVersion, + schemaConverter + ); + const database = await simpleDb.ensureDb(); + await fn(simpleDb, database.version, Array.from(database.objectStoreNames)); + await simpleDb.close(); } async function withUnstartedCustomPersistence( @@ -222,15 +204,6 @@ async function withForcedPersistence( ); } -function getAllObjectStores(db: IDBDatabase): string[] { - const objectStores: string[] = []; - for (let i = 0; i < db.objectStoreNames.length; ++i) { - objectStores.push(db.objectStoreNames.item(i)!); - } - objectStores.sort(); - return objectStores; -} - function addDocs( txn: SimpleDbTransaction, keys: string[], @@ -261,10 +234,10 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { after(() => SimpleDb.delete(INDEXEDDB_TEST_DATABASE_NAME)); it('can install schema version 1', () => { - return withDb(1, async db => { - expect(db.version).to.equal(1); + return withDb(1, async (db, version, objectStores) => { + expect(version).to.equal(1); // Version 1 adds all of the stores so far. - expect(getAllObjectStores(db)).to.have.members(V1_STORES); + expect(objectStores).to.have.members(V1_STORES); }); }); @@ -288,8 +261,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { ); return withDb(2, db => { - const sdb = new SimpleDb(db); - return sdb.runTransaction( + return db.runTransaction( 'readwrite', [DbTarget.store, DbTargetGlobal.store, DbMutationBatch.store], txn => { @@ -313,12 +285,11 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { } ); }).then(() => { - return withDb(3, db => { - expect(db.version).to.equal(3); - expect(getAllObjectStores(db)).to.have.members(V3_STORES); + return withDb(3, (db, version, objectStores) => { + expect(version).to.equal(3); + expect(objectStores).to.have.members(V3_STORES); - const sdb = new SimpleDb(db); - return sdb.runTransaction( + return db.runTransaction( 'readwrite', [DbTarget.store, DbTargetGlobal.store, DbMutationBatch.store], txn => { @@ -383,8 +354,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { ]; return withDb(3, db => { - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', [DbMutationBatch.store], txn => { + return db.runTransaction('readwrite', [DbMutationBatch.store], txn => { const store = txn.store(DbMutationBatch.store); return PersistencePromise.forEach( testMutations, @@ -392,12 +362,10 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { ); }); }).then(() => - withDb(4, db => { - expect(db.version).to.be.equal(4); - expect(getAllObjectStores(db)).to.have.members(V4_STORES); - - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', [DbMutationBatch.store], txn => { + withDb(4, (db, version, objectStores) => { + expect(version).to.be.equal(4); + expect(objectStores).to.have.members(V4_STORES); + return db.runTransaction('readwrite', [DbMutationBatch.store], txn => { const store = txn.store( DbMutationBatch.store ); @@ -487,9 +455,8 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { ]; return withDb(4, db => { - const sdb = new SimpleDb(db); // We can only use the V4 stores here, since that's as far as we've upgraded. - return sdb.runTransaction('readwrite', V4_STORES, txn => { + return db.runTransaction('readwrite', V4_STORES, txn => { const mutationBatchStore = txn.store< DbMutationBatchKey, DbMutationBatch @@ -533,12 +500,11 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { ); }); }).then(() => - withDb(5, db => { - expect(db.version).to.be.equal(5); + withDb(5, (db, version) => { + expect(version).to.be.equal(5); - const sdb = new SimpleDb(db); // There is no V5_STORES, continue using V4. - return sdb.runTransaction('readwrite', V4_STORES, txn => { + return db.runTransaction('readwrite', V4_STORES, txn => { const mutationBatchStore = txn.store< DbMutationBatchKey, DbMutationBatch @@ -589,8 +555,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { dbDoc: toDbRemoteDocument(TEST_SERIALIZER, doc, doc.version) })); // V5 stores doesn't exist - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', V4_STORES, txn => { + return db.runTransaction('readwrite', V4_STORES, txn => { const store = txn.store( DbRemoteDocument.store ); @@ -602,8 +567,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { }); }); await withDb(6, db => { - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', V6_STORES, txn => { + return db.runTransaction('readwrite', V6_STORES, txn => { const store = txn.store< DbRemoteDocumentGlobalKey, DbRemoteDocumentGlobal @@ -624,9 +588,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { const newSequenceNumber = 2; await withDb(6, db => { const serializer = TEST_SERIALIZER; - - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', V6_STORES, txn => { + return db.runTransaction('readwrite', V6_STORES, txn => { const targetGlobalStore = txn.store( DbTargetGlobal.store ); @@ -676,8 +638,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { // Now run the migration and verify await withDb(7, db => { - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', V6_STORES, txn => { + return db.runTransaction('readwrite', V6_STORES, txn => { const targetDocumentStore = txn.store< DbTargetDocumentKey, DbTargetDocument @@ -727,8 +688,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { }; await withDb(7, db => { - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', V6_STORES, txn => { + return db.runTransaction('readwrite', V6_STORES, txn => { const remoteDocumentStore = txn.store< DbRemoteDocumentKey, DbRemoteDocument @@ -764,8 +724,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { // Migrate to v8 and verify index entries. await withDb(8, db => { - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', V8_STORES, txn => { + return db.runTransaction('readwrite', V8_STORES, txn => { const collectionParentsStore = txn.store< DbCollectionParentKey, DbCollectionParent @@ -789,8 +748,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { it('rewrites canonical IDs during upgrade from version 9 to 10', async () => { await withDb(9, db => { - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', V8_STORES, txn => { + return db.runTransaction('readwrite', V8_STORES, txn => { const targetsStore = txn.store(DbTarget.store); const filteredQuery = query('collection', filter('foo', '==', 'bar')); @@ -809,8 +767,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { }); await withDb(10, db => { - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', V8_STORES, txn => { + return db.runTransaction('readwrite', V8_STORES, txn => { const targetsStore = txn.store(DbTarget.store); return targetsStore.iterate((key, value) => { const targetData = fromDbTarget(value).target; @@ -842,8 +799,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { ]; await withDb(8, db => { - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', V8_STORES, txn => { + return db.runTransaction('readwrite', V8_STORES, txn => { const remoteDocumentStore = txn.store< DbRemoteDocumentKey, DbRemoteDocument @@ -872,8 +828,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { // Migrate to v9 and verify that new documents are indexed. await withDb(9, db => { - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', V8_STORES, txn => { + return db.runTransaction('readwrite', V8_STORES, txn => { const remoteDocumentStore = txn.store< DbRemoteDocumentKey, DbRemoteDocument @@ -919,8 +874,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { const newDocPaths = ['coll/doc3', 'coll/doc4', 'abc/doc2']; await withDb(9, db => { - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', V8_STORES, txn => { + return db.runTransaction('readwrite', V8_STORES, txn => { return addDocs(txn, oldDocPaths, /* version= */ 1).next(() => addDocs(txn, newDocPaths, /* version= */ 2).next(() => { const remoteDocumentStore = txn.store< @@ -953,8 +907,7 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { const newDocPaths = ['coll1/new', 'coll2/new']; await withDb(9, db => { - const sdb = new SimpleDb(db); - return sdb.runTransaction('readwrite', V8_STORES, txn => { + return db.runTransaction('readwrite', V8_STORES, txn => { return addDocs(txn, oldDocPaths, /* version= */ 1).next(() => addDocs(txn, newDocPaths, /* version= */ 2).next(() => { const remoteDocumentStore = txn.store< @@ -981,19 +934,20 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { it('downgrading throws a custom error', async () => { // Upgrade to latest version - await withDb(SCHEMA_VERSION, async db => { - expect(db.version).to.equal(SCHEMA_VERSION); + await withDb(SCHEMA_VERSION, async (db, version) => { + expect(version).to.equal(SCHEMA_VERSION); }); // downgrade by one version const downgradeVersion = SCHEMA_VERSION - 1; const schemaConverter = new SchemaConverter(TEST_SERIALIZER); let error: FirestoreError | null = null; try { - await SimpleDb.openOrCreate( + const db = new SimpleDb( INDEXEDDB_TEST_DATABASE_NAME, downgradeVersion, schemaConverter ); + await db.ensureDb(); } catch (e) { error = e; expect( @@ -1011,7 +965,7 @@ describe('IndexedDb: canActAsPrimary', () => { } async function clearPrimaryLease(): Promise { - const simpleDb = await SimpleDb.openOrCreate( + const simpleDb = new SimpleDb( INDEXEDDB_TEST_DATABASE_NAME, SCHEMA_VERSION, new SchemaConverter(TEST_SERIALIZER) @@ -1026,7 +980,7 @@ describe('IndexedDb: canActAsPrimary', () => { } async function getCurrentLeaseOwner(): Promise { - const simpleDb = await SimpleDb.openOrCreate( + const simpleDb = new SimpleDb( INDEXEDDB_TEST_DATABASE_NAME, SCHEMA_VERSION, new SchemaConverter(TEST_SERIALIZER) @@ -1261,3 +1215,21 @@ describe('IndexedDb: allowTabSynchronization', () => { }); }); }); + +describe('IndexedDb', () => { + if (!IndexedDbPersistence.isAvailable()) { + console.warn('No IndexedDB. Skipping tests.'); + return; + } + + it('can re-open after close', async () => { + return withDb(2, async db => { + db.close(); + // Running a new IndexedDB transaction should re-open the database and not + // throw. + await db.runTransaction('readwrite', V1_STORES, () => + PersistencePromise.resolve() + ); + }); + }); +}); diff --git a/packages/firestore/test/unit/local/simple_db.test.ts b/packages/firestore/test/unit/local/simple_db.test.ts index 3dd7c09d7b4..b3a19f0fa2c 100644 --- a/packages/firestore/test/unit/local/simple_db.test.ts +++ b/packages/firestore/test/unit/local/simple_db.test.ts @@ -105,15 +105,10 @@ describe('SimpleDb', () => { ); } - beforeEach(() => { - return SimpleDb.delete(dbName) - .then(() => { - return SimpleDb.openOrCreate(dbName, 1, new TestSchemaConverter()); - }) - .then(simpleDb => { - db = simpleDb; - return writeTestData(); - }); + beforeEach(async () => { + await SimpleDb.delete(dbName); + db = new SimpleDb(dbName, 1, new TestSchemaConverter()); + await writeTestData(); }); afterEach(() => db.close()); diff --git a/packages/firestore/test/unit/specs/spec_test_runner.ts b/packages/firestore/test/unit/specs/spec_test_runner.ts index 504910c9c2f..5794253e53b 100644 --- a/packages/firestore/test/unit/specs/spec_test_runner.ts +++ b/packages/firestore/test/unit/specs/spec_test_runner.ts @@ -1547,7 +1547,7 @@ export interface StateExpectation { } async function clearCurrentPrimaryLease(): Promise { - const db = await SimpleDb.openOrCreate( + const db = new SimpleDb( INDEXEDDB_TEST_DATABASE_NAME, SCHEMA_VERSION, new SchemaConverter(TEST_SERIALIZER)