in packages/firestore/src/local/indexeddb_persistence.ts [535:635]
private canActAsPrimary(
txn: PersistenceTransaction
): PersistencePromise<boolean> {
if (this.forceOwningTab) {
return PersistencePromise.resolve<boolean>(true);
}
const store = primaryClientStore(txn);
return store
.get(DbPrimaryClient.key)
.next(currentPrimary => {
const currentLeaseIsValid =
currentPrimary !== null &&
this.isWithinAge(
currentPrimary.leaseTimestampMs,
MAX_PRIMARY_ELIGIBLE_AGE_MS
) &&
!this.isClientZombied(currentPrimary.ownerId);
// A client is eligible for the primary lease if:
// - its network is enabled and the client's tab is in the foreground.
// - its network is enabled and no other client's tab is in the
// foreground.
// - every clients network is disabled and the client's tab is in the
// foreground.
// - every clients network is disabled and no other client's tab is in
// the foreground.
// - the `forceOwningTab` setting was passed in.
if (currentLeaseIsValid) {
if (this.isLocalClient(currentPrimary) && this.networkEnabled) {
return true;
}
if (!this.isLocalClient(currentPrimary)) {
if (!currentPrimary!.allowTabSynchronization) {
// Fail the `canActAsPrimary` check if the current leaseholder has
// not opted into multi-tab synchronization. If this happens at
// client startup, we reject the Promise returned by
// `enablePersistence()` and the user can continue to use Firestore
// with in-memory persistence.
// If this fails during a lease refresh, we will instead block the
// AsyncQueue from executing further operations. Note that this is
// acceptable since mixing & matching different `synchronizeTabs`
// settings is not supported.
//
// TODO(b/114226234): Remove this check when `synchronizeTabs` can
// no longer be turned off.
throw new FirestoreError(
Code.FAILED_PRECONDITION,
PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG
);
}
return false;
}
}
if (this.networkEnabled && this.inForeground) {
return true;
}
return clientMetadataStore(txn)
.loadAll()
.next(existingClients => {
// Process all existing clients and determine whether at least one of
// them is better suited to obtain the primary lease.
const preferredCandidate = this.filterActiveClients(
existingClients,
MAX_PRIMARY_ELIGIBLE_AGE_MS
).find(otherClient => {
if (this.clientId !== otherClient.clientId) {
const otherClientHasBetterNetworkState =
!this.networkEnabled && otherClient.networkEnabled;
const otherClientHasBetterVisibility =
!this.inForeground && otherClient.inForeground;
const otherClientHasSameNetworkState =
this.networkEnabled === otherClient.networkEnabled;
if (
otherClientHasBetterNetworkState ||
(otherClientHasBetterVisibility &&
otherClientHasSameNetworkState)
) {
return true;
}
}
return false;
});
return preferredCandidate === undefined;
});
})
.next(canActAsPrimary => {
if (this.isPrimary !== canActAsPrimary) {
logDebug(
LOG_TAG,
`Client ${
canActAsPrimary ? 'is' : 'is not'
} eligible for a primary lease.`
);
}
return canActAsPrimary;
});
}