@@ -101,7 +101,7 @@ const MAX_PRIMARY_ELIGIBLE_AGE_MS = 5000;
101
101
const CLIENT_METADATA_REFRESH_INTERVAL_MS = 4000 ;
102
102
/** User-facing error when the primary lease is required but not available. */
103
103
const PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG =
104
- 'Another tab has exclusive access to the persistence layer. ' +
104
+ 'Failed to obtain exclusive access to the persistence layer. ' +
105
105
'To allow shared access, make sure to invoke ' +
106
106
'`enablePersistence()` with `synchronizeTabs:true` in all tabs.' ;
107
107
const UNSUPPORTED_PLATFORM_ERROR_MSG =
@@ -191,11 +191,12 @@ export class IndexedDbPersistence implements Persistence {
191
191
private readonly document : Document | null ;
192
192
private readonly window : Window ;
193
193
194
- // Technically these types should be `| undefined` because they are
194
+ // Technically `simpleDb` should be `| undefined` because it is
195
195
// initialized asynchronously by start(), but that would be more misleading
196
196
// than useful.
197
197
private simpleDb ! : SimpleDb ;
198
- private listenSequence ! : ListenSequence ;
198
+
199
+ private listenSequence : ListenSequence | null = null ;
199
200
200
201
private _started = false ;
201
202
private isPrimary = false ;
@@ -287,7 +288,15 @@ export class IndexedDbPersistence implements Persistence {
287
288
// having the persistence lock), so it's the first thing we should do.
288
289
return this . updateClientMetadataAndTryBecomePrimary ( ) ;
289
290
} )
290
- . then ( ( ) => {
291
+ . then ( e => {
292
+ if ( ! this . isPrimary && ! this . allowTabSynchronization ) {
293
+ // Fail `start()` if `synchronizeTabs` is disabled and we cannot
294
+ // obtain the primary lease.
295
+ throw new FirestoreError (
296
+ Code . FAILED_PRECONDITION ,
297
+ PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG
298
+ ) ;
299
+ }
291
300
this . attachVisibilityHandler ( ) ;
292
301
this . attachWindowUnloadHook ( ) ;
293
302
@@ -375,8 +384,10 @@ export class IndexedDbPersistence implements Persistence {
375
384
* primary lease.
376
385
*/
377
386
private updateClientMetadataAndTryBecomePrimary ( ) : Promise < void > {
378
- return this . simpleDb
379
- . runTransaction ( 'readwrite' , ALL_STORES , txn => {
387
+ return this . runTransaction (
388
+ 'updateClientMetadataAndTryBecomePrimary' ,
389
+ 'readwrite' ,
390
+ txn => {
380
391
const metadataStore = clientMetadataStore ( txn ) ;
381
392
return metadataStore
382
393
. put (
@@ -409,19 +420,19 @@ export class IndexedDbPersistence implements Persistence {
409
420
return /* canActAsPrimary= */ false ;
410
421
}
411
422
} ) ;
412
- } )
423
+ }
424
+ )
413
425
. catch ( e => {
414
426
if ( ! this . allowTabSynchronization ) {
415
427
if ( e . name === 'IndexedDbTransactionError' ) {
416
- logDebug ( LOG_TAG , "Failed to extend owner lease: " , e ) ;
417
- // Proceed in primary mode since the client was not initialized
418
- // to support multi-tab. Any subsequent access to IndexedDB will
419
- // verify the lease.
420
- return true ;
428
+ logDebug ( LOG_TAG , 'Failed to extend owner lease: ' , e ) ;
429
+ // Proceed with the existing state. Any subsequent access to
430
+ // IndexedDB will verify the lease.
431
+ return this . isPrimary ;
421
432
} else {
422
433
throw e ;
423
434
}
424
- }
435
+ }
425
436
426
437
logDebug (
427
438
LOG_TAG ,
@@ -441,7 +452,7 @@ export class IndexedDbPersistence implements Persistence {
441
452
}
442
453
443
454
private verifyPrimaryLease (
444
- txn : SimpleDbTransaction
455
+ txn : PersistenceTransaction
445
456
) : PersistencePromise < boolean > {
446
457
const store = primaryClientStore ( txn ) ;
447
458
return store . get ( DbPrimaryClient . key ) . next ( primaryClient => {
@@ -450,7 +461,7 @@ export class IndexedDbPersistence implements Persistence {
450
461
}
451
462
452
463
private removeClientMetadata (
453
- txn : SimpleDbTransaction
464
+ txn : PersistenceTransaction
454
465
) : PersistencePromise < void > {
455
466
const metadataStore = clientMetadataStore ( txn ) ;
456
467
return metadataStore . delete ( this . clientId ) ;
@@ -544,7 +555,7 @@ export class IndexedDbPersistence implements Persistence {
544
555
* (foreground) client should become leaseholder instead.
545
556
*/
546
557
private canActAsPrimary (
547
- txn : SimpleDbTransaction
558
+ txn : PersistenceTransaction
548
559
) : PersistencePromise < boolean > {
549
560
const store = primaryClientStore ( txn ) ;
550
561
return store
@@ -569,33 +580,10 @@ export class IndexedDbPersistence implements Persistence {
569
580
if ( currentLeaseIsValid ) {
570
581
if ( this . isLocalClient ( currentPrimary ) && this . networkEnabled ) {
571
582
return true ;
572
- }
573
-
574
- if ( ! this . isLocalClient ( currentPrimary ) ) {
575
- if ( ! currentPrimary ! . allowTabSynchronization ) {
576
- // Fail the `canActAsPrimary` check if the current leaseholder has
577
- // not opted into multi-tab synchronization. If this happens at
578
- // client startup, we reject the Promise returned by
579
- // `enablePersistence()` and the user can continue to use Firestore
580
- // with in-memory persistence.
581
- // If this fails during a lease refresh, we will instead block the
582
- // AsyncQueue from executing further operations. Note that this is
583
- // acceptable since mixing & matching different `synchronizeTabs`
584
- // settings is not supported.
585
- //
586
- // TODO(b/114226234): Remove this check when `synchronizeTabs` can
587
- // no longer be turned off.
588
- throw new FirestoreError (
589
- Code . FAILED_PRECONDITION ,
590
- PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG
591
- ) ;
592
- }
593
-
583
+ } else if ( ! this . isLocalClient ( currentPrimary ) ) {
594
584
return false ;
595
585
}
596
- }
597
-
598
- if ( this . networkEnabled && this . inForeground ) {
586
+ } else if ( this . networkEnabled && this . inForeground ) {
599
587
return true ;
600
588
}
601
589
@@ -653,15 +641,11 @@ export class IndexedDbPersistence implements Persistence {
653
641
}
654
642
this . detachVisibilityHandler ( ) ;
655
643
this . detachWindowUnloadHook ( ) ;
656
- await this . simpleDb . runTransaction (
657
- 'readwrite' ,
658
- [ DbPrimaryClient . store , DbClientMetadata . store ] ,
659
- txn => {
660
- return this . releasePrimaryLeaseIfHeld ( txn ) . next ( ( ) =>
661
- this . removeClientMetadata ( txn )
662
- ) ;
663
- }
664
- ) ;
644
+ await this . runTransaction ( 'shutdown' , 'readwrite' , txn => {
645
+ return this . releasePrimaryLeaseIfHeld ( txn ) . next ( ( ) =>
646
+ this . removeClientMetadata ( txn )
647
+ ) ;
648
+ } ) ;
665
649
this . simpleDb . close ( ) ;
666
650
667
651
// Remove the entry marking the client as zombied from LocalStorage since
@@ -692,19 +676,15 @@ export class IndexedDbPersistence implements Persistence {
692
676
* PORTING NOTE: This is only used for Web multi-tab.
693
677
*/
694
678
getActiveClients ( ) : Promise < ClientId [ ] > {
695
- return this . simpleDb . runTransaction (
696
- 'readonly' ,
697
- [ DbClientMetadata . store ] ,
698
- txn => {
699
- return clientMetadataStore ( txn )
700
- . loadAll ( )
701
- . next ( clients =>
702
- this . filterActiveClients ( clients , MAX_CLIENT_AGE_MS ) . map (
703
- clientMetadata => clientMetadata . clientId
704
- )
705
- ) ;
706
- }
707
- ) ;
679
+ return this . runTransaction ( 'getActiveClients' , 'readonly' , txn => {
680
+ return clientMetadataStore ( txn )
681
+ . loadAll ( )
682
+ . next ( clients =>
683
+ this . filterActiveClients ( clients , MAX_CLIENT_AGE_MS ) . map (
684
+ clientMetadata => clientMetadata . clientId
685
+ )
686
+ ) ;
687
+ } ) ;
708
688
}
709
689
710
690
static async clearPersistence ( persistenceKey : string ) : Promise < void > {
@@ -775,7 +755,9 @@ export class IndexedDbPersistence implements Persistence {
775
755
. runTransaction ( simpleDbMode , ALL_STORES , simpleDbTxn => {
776
756
persistenceTransaction = new IndexedDbTransaction (
777
757
simpleDbTxn ,
778
- this . listenSequence . next ( )
758
+ this . listenSequence
759
+ ? this . listenSequence . next ( )
760
+ : ListenSequence . INVALID
779
761
) ;
780
762
781
763
if ( mode === 'readwrite-primary' ) {
@@ -784,12 +766,12 @@ export class IndexedDbPersistence implements Persistence {
784
766
// executing transactionOperation(). This ensures that even if the
785
767
// transactionOperation takes a long time, we'll use a recent
786
768
// leaseTimestampMs in the extended (or newly acquired) lease.
787
- return this . verifyPrimaryLease ( simpleDbTxn )
769
+ return this . verifyPrimaryLease ( persistenceTransaction )
788
770
. next ( holdsPrimaryLease => {
789
771
if ( holdsPrimaryLease ) {
790
772
return /* holdsPrimaryLease= */ true ;
791
773
}
792
- return this . canActAsPrimary ( simpleDbTxn ) ;
774
+ return this . canActAsPrimary ( persistenceTransaction ) ;
793
775
} )
794
776
. next ( holdsPrimaryLease => {
795
777
if ( ! holdsPrimaryLease ) {
@@ -808,14 +790,14 @@ export class IndexedDbPersistence implements Persistence {
808
790
return transactionOperation ( persistenceTransaction ) ;
809
791
} )
810
792
. next ( result => {
811
- return this . acquireOrExtendPrimaryLease ( simpleDbTxn ) . next (
812
- ( ) => result
813
- ) ;
793
+ return this . acquireOrExtendPrimaryLease (
794
+ persistenceTransaction
795
+ ) . next ( ( ) => result ) ;
814
796
} ) ;
815
797
} else {
816
- return this . verifyAllowTabSynchronization ( simpleDbTxn ) . next ( ( ) =>
817
- transactionOperation ( persistenceTransaction )
818
- ) ;
798
+ return this . verifyAllowTabSynchronization (
799
+ persistenceTransaction
800
+ ) . next ( ( ) => transactionOperation ( persistenceTransaction ) ) ;
819
801
}
820
802
} )
821
803
. then ( result => {
@@ -831,7 +813,7 @@ export class IndexedDbPersistence implements Persistence {
831
813
// TODO(b/114226234): Remove this check when `synchronizeTabs` can no longer
832
814
// be turned off.
833
815
private verifyAllowTabSynchronization (
834
- txn : SimpleDbTransaction
816
+ txn : PersistenceTransaction
835
817
) : PersistencePromise < void > {
836
818
const store = primaryClientStore ( txn ) ;
837
819
return store . get ( DbPrimaryClient . key ) . next ( currentPrimary => {
@@ -844,7 +826,10 @@ export class IndexedDbPersistence implements Persistence {
844
826
! this . isClientZombied ( currentPrimary . ownerId ) ;
845
827
846
828
if ( currentLeaseIsValid && ! this . isLocalClient ( currentPrimary ) ) {
847
- if ( ! currentPrimary ! . allowTabSynchronization ) {
829
+ if (
830
+ ! this . allowTabSynchronization ||
831
+ ! currentPrimary ! . allowTabSynchronization
832
+ ) {
848
833
throw new FirestoreError (
849
834
Code . FAILED_PRECONDITION ,
850
835
PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG
@@ -859,7 +844,7 @@ export class IndexedDbPersistence implements Persistence {
859
844
* method does not verify that the client is eligible for this lease.
860
845
*/
861
846
private acquireOrExtendPrimaryLease (
862
- txn : SimpleDbTransaction
847
+ txn : PersistenceTransaction
863
848
) : PersistencePromise < void > {
864
849
const newPrimary = new DbPrimaryClient (
865
850
this . clientId ,
@@ -895,7 +880,7 @@ export class IndexedDbPersistence implements Persistence {
895
880
896
881
/** Checks the primary lease and removes it if we are the current primary. */
897
882
private releasePrimaryLeaseIfHeld (
898
- txn : SimpleDbTransaction
883
+ txn : PersistenceTransaction
899
884
) : PersistencePromise < void > {
900
885
const store = primaryClientStore ( txn ) ;
901
886
return store . get ( DbPrimaryClient . key ) . next ( primaryClient => {
@@ -1060,18 +1045,22 @@ export class IndexedDbPersistence implements Persistence {
1060
1045
* Helper to get a typed SimpleDbStore for the primary client object store.
1061
1046
*/
1062
1047
function primaryClientStore (
1063
- txn : SimpleDbTransaction
1048
+ txn : PersistenceTransaction
1064
1049
) : SimpleDbStore < DbPrimaryClientKey , DbPrimaryClient > {
1065
- return txn . store < DbPrimaryClientKey , DbPrimaryClient > ( DbPrimaryClient . store ) ;
1050
+ return IndexedDbPersistence . getStore < DbPrimaryClientKey , DbPrimaryClient > (
1051
+ txn ,
1052
+ DbPrimaryClient . store
1053
+ ) ;
1066
1054
}
1067
1055
1068
1056
/**
1069
1057
* Helper to get a typed SimpleDbStore for the client metadata object store.
1070
1058
*/
1071
1059
function clientMetadataStore (
1072
- txn : SimpleDbTransaction
1060
+ txn : PersistenceTransaction
1073
1061
) : SimpleDbStore < DbClientMetadataKey , DbClientMetadata > {
1074
- return txn . store < DbClientMetadataKey , DbClientMetadata > (
1062
+ return IndexedDbPersistence . getStore < DbClientMetadataKey , DbClientMetadata > (
1063
+ txn ,
1075
1064
DbClientMetadata . store
1076
1065
) ;
1077
1066
}
0 commit comments