@@ -100,7 +100,7 @@ const MAX_PRIMARY_ELIGIBLE_AGE_MS = 5000;
100
100
const CLIENT_METADATA_REFRESH_INTERVAL_MS = 4000 ;
101
101
/** User-facing error when the primary lease is required but not available. */
102
102
const PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG =
103
- 'Another tab has exclusive access to the persistence layer. ' +
103
+ 'Failed to obtain exclusive access to the persistence layer. ' +
104
104
'To allow shared access, make sure to invoke ' +
105
105
'`enablePersistence()` with `synchronizeTabs:true` in all tabs.' ;
106
106
const UNSUPPORTED_PLATFORM_ERROR_MSG =
@@ -190,11 +190,12 @@ export class IndexedDbPersistence implements Persistence {
190
190
private readonly document : Document | null ;
191
191
private readonly window : Window ;
192
192
193
- // Technically these types should be `| undefined` because they are
193
+ // Technically `simpleDb` should be `| undefined` because it is
194
194
// initialized asynchronously by start(), but that would be more misleading
195
195
// than useful.
196
196
private simpleDb ! : SimpleDb ;
197
- private listenSequence ! : ListenSequence ;
197
+
198
+ private listenSequence : ListenSequence | null = null ;
198
199
199
200
private _started = false ;
200
201
private isPrimary = false ;
@@ -286,7 +287,15 @@ export class IndexedDbPersistence implements Persistence {
286
287
// having the persistence lock), so it's the first thing we should do.
287
288
return this . updateClientMetadataAndTryBecomePrimary ( ) ;
288
289
} )
289
- . then ( ( ) => {
290
+ . then ( e => {
291
+ if ( ! this . isPrimary && ! this . allowTabSynchronization ) {
292
+ // Fail `start()` if `synchronizeTabs` is disabled and we cannot
293
+ // obtain the primary lease.
294
+ throw new FirestoreError (
295
+ Code . FAILED_PRECONDITION ,
296
+ PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG
297
+ ) ;
298
+ }
290
299
this . attachVisibilityHandler ( ) ;
291
300
this . attachWindowUnloadHook ( ) ;
292
301
@@ -374,8 +383,10 @@ export class IndexedDbPersistence implements Persistence {
374
383
* primary lease.
375
384
*/
376
385
private updateClientMetadataAndTryBecomePrimary ( ) : Promise < void > {
377
- return this . simpleDb
378
- . runTransaction ( 'readwrite' , ALL_STORES , txn => {
386
+ return this . runTransaction (
387
+ 'updateClientMetadataAndTryBecomePrimary' ,
388
+ 'readwrite' ,
389
+ txn => {
379
390
const metadataStore = clientMetadataStore ( txn ) ;
380
391
return metadataStore
381
392
. put (
@@ -408,10 +419,18 @@ export class IndexedDbPersistence implements Persistence {
408
419
return /* canActAsPrimary= */ false ;
409
420
}
410
421
} ) ;
411
- } )
422
+ }
423
+ )
412
424
. catch ( e => {
413
425
if ( ! this . allowTabSynchronization ) {
414
- throw e ;
426
+ if ( e . name === 'IndexedDbTransactionError' ) {
427
+ logDebug ( LOG_TAG , 'Failed to extend owner lease: ' , e ) ;
428
+ // Proceed with the existing state. Any subsequent access to
429
+ // IndexedDB will verify the lease.
430
+ return this . isPrimary ;
431
+ } else {
432
+ throw e ;
433
+ }
415
434
}
416
435
417
436
logDebug (
@@ -432,7 +451,7 @@ export class IndexedDbPersistence implements Persistence {
432
451
}
433
452
434
453
private verifyPrimaryLease (
435
- txn : SimpleDbTransaction
454
+ txn : PersistenceTransaction
436
455
) : PersistencePromise < boolean > {
437
456
const store = primaryClientStore ( txn ) ;
438
457
return store . get ( DbPrimaryClient . key ) . next ( primaryClient => {
@@ -441,7 +460,7 @@ export class IndexedDbPersistence implements Persistence {
441
460
}
442
461
443
462
private removeClientMetadata (
444
- txn : SimpleDbTransaction
463
+ txn : PersistenceTransaction
445
464
) : PersistencePromise < void > {
446
465
const metadataStore = clientMetadataStore ( txn ) ;
447
466
return metadataStore . delete ( this . clientId ) ;
@@ -535,7 +554,7 @@ export class IndexedDbPersistence implements Persistence {
535
554
* (foreground) client should become leaseholder instead.
536
555
*/
537
556
private canActAsPrimary (
538
- txn : SimpleDbTransaction
557
+ txn : PersistenceTransaction
539
558
) : PersistencePromise < boolean > {
540
559
const store = primaryClientStore ( txn ) ;
541
560
return store
@@ -644,15 +663,13 @@ export class IndexedDbPersistence implements Persistence {
644
663
}
645
664
this . detachVisibilityHandler ( ) ;
646
665
this . detachWindowUnloadHook ( ) ;
647
- await this . simpleDb . runTransaction (
648
- 'readwrite' ,
649
- [ DbPrimaryClient . store , DbClientMetadata . store ] ,
650
- txn => {
651
- return this . releasePrimaryLeaseIfHeld ( txn ) . next ( ( ) =>
652
- this . removeClientMetadata ( txn )
653
- ) ;
654
- }
655
- ) ;
666
+ await this . runTransaction ( 'shutdown' , 'readwrite' , txn => {
667
+ return this . releasePrimaryLeaseIfHeld ( txn ) . next ( ( ) =>
668
+ this . removeClientMetadata ( txn )
669
+ ) ;
670
+ } ) . catch ( e => {
671
+ logDebug ( LOG_TAG , 'Proceeding with shutdown despite failure: ' , e ) ;
672
+ } ) ;
656
673
this . simpleDb . close ( ) ;
657
674
658
675
// Remove the entry marking the client as zombied from LocalStorage since
@@ -683,19 +700,15 @@ export class IndexedDbPersistence implements Persistence {
683
700
* PORTING NOTE: This is only used for Web multi-tab.
684
701
*/
685
702
getActiveClients ( ) : Promise < ClientId [ ] > {
686
- return this . simpleDb . runTransaction (
687
- 'readonly' ,
688
- [ DbClientMetadata . store ] ,
689
- txn => {
690
- return clientMetadataStore ( txn )
691
- . loadAll ( )
692
- . next ( clients =>
693
- this . filterActiveClients ( clients , MAX_CLIENT_AGE_MS ) . map (
694
- clientMetadata => clientMetadata . clientId
695
- )
696
- ) ;
697
- }
698
- ) ;
703
+ return this . runTransaction ( 'getActiveClients' , 'readonly' , txn => {
704
+ return clientMetadataStore ( txn )
705
+ . loadAll ( )
706
+ . next ( clients =>
707
+ this . filterActiveClients ( clients , MAX_CLIENT_AGE_MS ) . map (
708
+ clientMetadata => clientMetadata . clientId
709
+ )
710
+ ) ;
711
+ } ) ;
699
712
}
700
713
701
714
static async clearPersistence ( persistenceKey : string ) : Promise < void > {
@@ -766,7 +779,9 @@ export class IndexedDbPersistence implements Persistence {
766
779
. runTransaction ( simpleDbMode , ALL_STORES , simpleDbTxn => {
767
780
persistenceTransaction = new IndexedDbTransaction (
768
781
simpleDbTxn ,
769
- this . listenSequence . next ( )
782
+ this . listenSequence
783
+ ? this . listenSequence . next ( )
784
+ : ListenSequence . INVALID
770
785
) ;
771
786
772
787
if ( mode === 'readwrite-primary' ) {
@@ -775,12 +790,12 @@ export class IndexedDbPersistence implements Persistence {
775
790
// executing transactionOperation(). This ensures that even if the
776
791
// transactionOperation takes a long time, we'll use a recent
777
792
// leaseTimestampMs in the extended (or newly acquired) lease.
778
- return this . verifyPrimaryLease ( simpleDbTxn )
793
+ return this . verifyPrimaryLease ( persistenceTransaction )
779
794
. next ( holdsPrimaryLease => {
780
795
if ( holdsPrimaryLease ) {
781
796
return /* holdsPrimaryLease= */ true ;
782
797
}
783
- return this . canActAsPrimary ( simpleDbTxn ) ;
798
+ return this . canActAsPrimary ( persistenceTransaction ) ;
784
799
} )
785
800
. next ( holdsPrimaryLease => {
786
801
if ( ! holdsPrimaryLease ) {
@@ -799,14 +814,14 @@ export class IndexedDbPersistence implements Persistence {
799
814
return transactionOperation ( persistenceTransaction ) ;
800
815
} )
801
816
. next ( result => {
802
- return this . acquireOrExtendPrimaryLease ( simpleDbTxn ) . next (
803
- ( ) => result
804
- ) ;
817
+ return this . acquireOrExtendPrimaryLease (
818
+ persistenceTransaction
819
+ ) . next ( ( ) => result ) ;
805
820
} ) ;
806
821
} else {
807
- return this . verifyAllowTabSynchronization ( simpleDbTxn ) . next ( ( ) =>
808
- transactionOperation ( persistenceTransaction )
809
- ) ;
822
+ return this . verifyAllowTabSynchronization (
823
+ persistenceTransaction
824
+ ) . next ( ( ) => transactionOperation ( persistenceTransaction ) ) ;
810
825
}
811
826
} )
812
827
. then ( result => {
@@ -822,7 +837,7 @@ export class IndexedDbPersistence implements Persistence {
822
837
// TODO(b/114226234): Remove this check when `synchronizeTabs` can no longer
823
838
// be turned off.
824
839
private verifyAllowTabSynchronization (
825
- txn : SimpleDbTransaction
840
+ txn : PersistenceTransaction
826
841
) : PersistencePromise < void > {
827
842
const store = primaryClientStore ( txn ) ;
828
843
return store . get ( DbPrimaryClient . key ) . next ( currentPrimary => {
@@ -835,7 +850,10 @@ export class IndexedDbPersistence implements Persistence {
835
850
! this . isClientZombied ( currentPrimary . ownerId ) ;
836
851
837
852
if ( currentLeaseIsValid && ! this . isLocalClient ( currentPrimary ) ) {
838
- if ( ! currentPrimary ! . allowTabSynchronization ) {
853
+ if (
854
+ ! this . allowTabSynchronization ||
855
+ ! currentPrimary ! . allowTabSynchronization
856
+ ) {
839
857
throw new FirestoreError (
840
858
Code . FAILED_PRECONDITION ,
841
859
PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG
@@ -850,7 +868,7 @@ export class IndexedDbPersistence implements Persistence {
850
868
* method does not verify that the client is eligible for this lease.
851
869
*/
852
870
private acquireOrExtendPrimaryLease (
853
- txn : SimpleDbTransaction
871
+ txn : PersistenceTransaction
854
872
) : PersistencePromise < void > {
855
873
const newPrimary = new DbPrimaryClient (
856
874
this . clientId ,
@@ -886,7 +904,7 @@ export class IndexedDbPersistence implements Persistence {
886
904
887
905
/** Checks the primary lease and removes it if we are the current primary. */
888
906
private releasePrimaryLeaseIfHeld (
889
- txn : SimpleDbTransaction
907
+ txn : PersistenceTransaction
890
908
) : PersistencePromise < void > {
891
909
const store = primaryClientStore ( txn ) ;
892
910
return store . get ( DbPrimaryClient . key ) . next ( primaryClient => {
@@ -1051,18 +1069,22 @@ export class IndexedDbPersistence implements Persistence {
1051
1069
* Helper to get a typed SimpleDbStore for the primary client object store.
1052
1070
*/
1053
1071
function primaryClientStore (
1054
- txn : SimpleDbTransaction
1072
+ txn : PersistenceTransaction
1055
1073
) : SimpleDbStore < DbPrimaryClientKey , DbPrimaryClient > {
1056
- return txn . store < DbPrimaryClientKey , DbPrimaryClient > ( DbPrimaryClient . store ) ;
1074
+ return IndexedDbPersistence . getStore < DbPrimaryClientKey , DbPrimaryClient > (
1075
+ txn ,
1076
+ DbPrimaryClient . store
1077
+ ) ;
1057
1078
}
1058
1079
1059
1080
/**
1060
1081
* Helper to get a typed SimpleDbStore for the client metadata object store.
1061
1082
*/
1062
1083
function clientMetadataStore (
1063
- txn : SimpleDbTransaction
1084
+ txn : PersistenceTransaction
1064
1085
) : SimpleDbStore < DbClientMetadataKey , DbClientMetadata > {
1065
- return txn . store < DbClientMetadataKey , DbClientMetadata > (
1086
+ return IndexedDbPersistence . getStore < DbClientMetadataKey , DbClientMetadata > (
1087
+ txn ,
1066
1088
DbClientMetadata . store
1067
1089
) ;
1068
1090
}
0 commit comments