diff --git a/packages/firestore/src/core/sync_engine.ts b/packages/firestore/src/core/sync_engine.ts index 2eccb47d2a2..e1b7931e5c2 100644 --- a/packages/firestore/src/core/sync_engine.ts +++ b/packages/firestore/src/core/sync_engine.ts @@ -16,7 +16,7 @@ */ import { User } from '../auth/user'; -import { LocalStore } from '../local/local_store'; +import { ignoreIfPrimaryLeaseLoss, LocalStore } from '../local/local_store'; import { LocalViewChanges } from '../local/local_view_changes'; import { ReferenceSet } from '../local/reference_set'; import { TargetData, TargetPurpose } from '../local/target_data'; @@ -40,7 +40,6 @@ import { ObjectMap } from '../util/obj_map'; import { Deferred } from '../util/promise'; import { SortedMap } from '../util/sorted_map'; -import { ignoreIfPrimaryLeaseLoss } from '../local/indexeddb_persistence'; import { ClientId, SharedClientState } from '../local/shared_client_state'; import { QueryTargetState, diff --git a/packages/firestore/src/local/indexeddb_persistence.ts b/packages/firestore/src/local/indexeddb_persistence.ts index f59a7f546fa..8d77ede55fb 100644 --- a/packages/firestore/src/local/indexeddb_persistence.ts +++ b/packages/firestore/src/local/indexeddb_persistence.ts @@ -63,6 +63,7 @@ import { Persistence, PersistenceTransaction, PersistenceTransactionMode, + PRIMARY_LEASE_LOST_ERROR_MSG, PrimaryStateListener, ReferenceDelegate } from './persistence'; @@ -97,9 +98,6 @@ const MAX_PRIMARY_ELIGIBLE_AGE_MS = 5000; */ const CLIENT_METADATA_REFRESH_INTERVAL_MS = 4000; /** User-facing error when the primary lease is required but not available. */ -const PRIMARY_LEASE_LOST_ERROR_MSG = - 'The current tab is not in the required state to perform this operation. ' + - 'It might be necessary to refresh the browser tab.'; const PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG = 'Another tab has exclusive access to the persistence layer. ' + 'To allow shared access, make sure to invoke ' + @@ -1052,33 +1050,6 @@ export class IndexedDbPersistence implements Persistence { } } -function isPrimaryLeaseLostError(err: FirestoreError): boolean { - return ( - err.code === Code.FAILED_PRECONDITION && - err.message === PRIMARY_LEASE_LOST_ERROR_MSG - ); -} - -/** - * Verifies the error thrown by a LocalStore operation. If a LocalStore - * operation fails because the primary lease has been taken by another client, - * we ignore the error (the persistence layer will immediately call - * `applyPrimaryLease` to propagate the primary state change). All other errors - * are re-thrown. - * - * @param err An error returned by a LocalStore operation. - * @return A Promise that resolves after we recovered, or the original error. - */ -export async function ignoreIfPrimaryLeaseLoss( - err: FirestoreError -): Promise { - if (isPrimaryLeaseLostError(err)) { - log.debug(LOG_TAG, 'Unexpectedly lost primary lease'); - } else { - throw err; - } -} - /** * Helper to get a typed SimpleDbStore for the primary client object store. */ diff --git a/packages/firestore/src/local/local_store.ts b/packages/firestore/src/local/local_store.ts index d8cf674be76..6ec65cd6531 100644 --- a/packages/firestore/src/local/local_store.ts +++ b/packages/firestore/src/local/local_store.ts @@ -38,6 +38,7 @@ import { } from '../model/mutation_batch'; import { RemoteEvent, TargetChange } from '../remote/remote_event'; import { assert } from '../util/assert'; +import { Code, FirestoreError } from '../util/error'; import * as log from '../util/log'; import { primitiveComparator } from '../util/misc'; import * as objUtils from '../util/obj'; @@ -49,7 +50,11 @@ import { LocalViewChanges } from './local_view_changes'; import { LruGarbageCollector, LruResults } from './lru_garbage_collector'; import { IndexedDbRemoteDocumentCache } from './indexeddb_remote_document_cache'; import { MutationQueue } from './mutation_queue'; -import { Persistence, PersistenceTransaction } from './persistence'; +import { + Persistence, + PersistenceTransaction, + PRIMARY_LEASE_LOST_ERROR_MSG +} from './persistence'; import { PersistencePromise } from './persistence_promise'; import { TargetCache } from './target_cache'; import { QueryEngine } from './query_engine'; @@ -1121,3 +1126,26 @@ export class LocalStore { } } } + +/** + * Verifies the error thrown by a LocalStore operation. If a LocalStore + * operation fails because the primary lease has been taken by another client, + * we ignore the error (the persistence layer will immediately call + * `applyPrimaryLease` to propagate the primary state change). All other errors + * are re-thrown. + * + * @param err An error returned by a LocalStore operation. + * @return A Promise that resolves after we recovered, or the original error. + */ +export async function ignoreIfPrimaryLeaseLoss( + err: FirestoreError +): Promise { + if ( + err.code === Code.FAILED_PRECONDITION && + err.message === PRIMARY_LEASE_LOST_ERROR_MSG + ) { + log.debug(LOG_TAG, 'Unexpectedly lost primary lease'); + } else { + throw err; + } +} diff --git a/packages/firestore/src/local/lru_garbage_collector.ts b/packages/firestore/src/local/lru_garbage_collector.ts index ede5058aa3b..6890feb2bbc 100644 --- a/packages/firestore/src/local/lru_garbage_collector.ts +++ b/packages/firestore/src/local/lru_garbage_collector.ts @@ -24,8 +24,7 @@ import { primitiveComparator } from '../util/misc'; import { CancelablePromise } from '../util/promise'; import { SortedMap } from '../util/sorted_map'; import { SortedSet } from '../util/sorted_set'; -import { ignoreIfPrimaryLeaseLoss } from './indexeddb_persistence'; -import { LocalStore } from './local_store'; +import { ignoreIfPrimaryLeaseLoss, LocalStore } from './local_store'; import { PersistenceTransaction } from './persistence'; import { PersistencePromise } from './persistence_promise'; import { TargetData } from './target_data'; diff --git a/packages/firestore/src/local/persistence.ts b/packages/firestore/src/local/persistence.ts index 01e1d9365f3..4421b4ed6a1 100644 --- a/packages/firestore/src/local/persistence.ts +++ b/packages/firestore/src/local/persistence.ts @@ -28,6 +28,10 @@ import { RemoteDocumentCache } from './remote_document_cache'; import { ClientId } from './shared_client_state'; import { TargetData } from './target_data'; +export const PRIMARY_LEASE_LOST_ERROR_MSG = + 'The current tab is not in the required state to perform this operation. ' + + 'It might be necessary to refresh the browser tab.'; + /** * A base class representing a persistence transaction, encapsulating both the * transaction's sequence numbers as well as a list of onCommitted listeners. diff --git a/packages/firestore/src/remote/remote_store.ts b/packages/firestore/src/remote/remote_store.ts index e73716288ed..73d6b930bcc 100644 --- a/packages/firestore/src/remote/remote_store.ts +++ b/packages/firestore/src/remote/remote_store.ts @@ -18,7 +18,7 @@ import { SnapshotVersion } from '../core/snapshot_version'; import { Transaction } from '../core/transaction'; import { OnlineState, TargetId } from '../core/types'; -import { LocalStore } from '../local/local_store'; +import { ignoreIfPrimaryLeaseLoss, LocalStore } from '../local/local_store'; import { TargetData, TargetPurpose } from '../local/target_data'; import { MutationResult } from '../model/mutation'; import { @@ -32,7 +32,6 @@ import { FirestoreError } from '../util/error'; import * as log from '../util/log'; import * as objUtils from '../util/obj'; -import { ignoreIfPrimaryLeaseLoss } from '../local/indexeddb_persistence'; import { DocumentKeySet } from '../model/collections'; import { AsyncQueue } from '../util/async_queue'; import { ConnectivityMonitor, NetworkStatus } from './connectivity_monitor'; diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index accf56f818c..6b397f9ae9a 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -814,7 +814,7 @@ apiDescribe('Database', (persistence: boolean) => { // eslint-disable-next-line no-restricted-properties describe.skip('Listens are rejected remotely:', () => { //eslint-disable-next-line @typescript-eslint/no-explicit-any - const queryForRejection : firestore.Query = null as any; + const queryForRejection: firestore.Query = null as any; it('will reject listens', () => { const deferred = new Deferred();