@@ -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,10 +420,18 @@ 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
- throw e ;
427
+ if ( e . name === 'IndexedDbTransactionError' ) {
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 ;
432
+ } else {
433
+ throw e ;
434
+ }
416
435
}
417
436
418
437
logDebug (
@@ -433,7 +452,7 @@ export class IndexedDbPersistence implements Persistence {
433
452
}
434
453
435
454
private verifyPrimaryLease (
436
- txn : SimpleDbTransaction
455
+ txn : PersistenceTransaction
437
456
) : PersistencePromise < boolean > {
438
457
const store = primaryClientStore ( txn ) ;
439
458
return store . get ( DbPrimaryClient . key ) . next ( primaryClient => {
@@ -442,7 +461,7 @@ export class IndexedDbPersistence implements Persistence {
442
461
}
443
462
444
463
private removeClientMetadata (
445
- txn : SimpleDbTransaction
464
+ txn : PersistenceTransaction
446
465
) : PersistencePromise < void > {
447
466
const metadataStore = clientMetadataStore ( txn ) ;
448
467
return metadataStore . delete ( this . clientId ) ;
@@ -536,7 +555,7 @@ export class IndexedDbPersistence implements Persistence {
536
555
* (foreground) client should become leaseholder instead.
537
556
*/
538
557
private canActAsPrimary (
539
- txn : SimpleDbTransaction
558
+ txn : PersistenceTransaction
540
559
) : PersistencePromise < boolean > {
541
560
const store = primaryClientStore ( txn ) ;
542
561
return store
@@ -645,15 +664,13 @@ export class IndexedDbPersistence implements Persistence {
645
664
}
646
665
this . detachVisibilityHandler ( ) ;
647
666
this . detachWindowUnloadHook ( ) ;
648
- await this . simpleDb . runTransaction (
649
- 'readwrite' ,
650
- [ DbPrimaryClient . store , DbClientMetadata . store ] ,
651
- txn => {
652
- return this . releasePrimaryLeaseIfHeld ( txn ) . next ( ( ) =>
653
- this . removeClientMetadata ( txn )
654
- ) ;
655
- }
656
- ) ;
667
+ await this . runTransaction ( 'shutdown' , 'readwrite' , txn => {
668
+ return this . releasePrimaryLeaseIfHeld ( txn ) . next ( ( ) =>
669
+ this . removeClientMetadata ( txn )
670
+ ) ;
671
+ } ) . catch ( e => {
672
+ logDebug ( LOG_TAG , 'Proceeding with shutdown despite failure: ' , e ) ;
673
+ } ) ;
657
674
this . simpleDb . close ( ) ;
658
675
659
676
// Remove the entry marking the client as zombied from LocalStorage since
@@ -684,19 +701,15 @@ export class IndexedDbPersistence implements Persistence {
684
701
* PORTING NOTE: This is only used for Web multi-tab.
685
702
*/
686
703
getActiveClients ( ) : Promise < ClientId [ ] > {
687
- return this . simpleDb . runTransaction (
688
- 'readonly' ,
689
- [ DbClientMetadata . store ] ,
690
- txn => {
691
- return clientMetadataStore ( txn )
692
- . loadAll ( )
693
- . next ( clients =>
694
- this . filterActiveClients ( clients , MAX_CLIENT_AGE_MS ) . map (
695
- clientMetadata => clientMetadata . clientId
696
- )
697
- ) ;
698
- }
699
- ) ;
704
+ return this . runTransaction ( 'getActiveClients' , 'readonly' , txn => {
705
+ return clientMetadataStore ( txn )
706
+ . loadAll ( )
707
+ . next ( clients =>
708
+ this . filterActiveClients ( clients , MAX_CLIENT_AGE_MS ) . map (
709
+ clientMetadata => clientMetadata . clientId
710
+ )
711
+ ) ;
712
+ } ) ;
700
713
}
701
714
702
715
static async clearPersistence ( persistenceKey : string ) : Promise < void > {
@@ -767,7 +780,9 @@ export class IndexedDbPersistence implements Persistence {
767
780
. runTransaction ( simpleDbMode , ALL_STORES , simpleDbTxn => {
768
781
persistenceTransaction = new IndexedDbTransaction (
769
782
simpleDbTxn ,
770
- this . listenSequence . next ( )
783
+ this . listenSequence
784
+ ? this . listenSequence . next ( )
785
+ : ListenSequence . INVALID
771
786
) ;
772
787
773
788
if ( mode === 'readwrite-primary' ) {
@@ -776,12 +791,12 @@ export class IndexedDbPersistence implements Persistence {
776
791
// executing transactionOperation(). This ensures that even if the
777
792
// transactionOperation takes a long time, we'll use a recent
778
793
// leaseTimestampMs in the extended (or newly acquired) lease.
779
- return this . verifyPrimaryLease ( simpleDbTxn )
794
+ return this . verifyPrimaryLease ( persistenceTransaction )
780
795
. next ( holdsPrimaryLease => {
781
796
if ( holdsPrimaryLease ) {
782
797
return /* holdsPrimaryLease= */ true ;
783
798
}
784
- return this . canActAsPrimary ( simpleDbTxn ) ;
799
+ return this . canActAsPrimary ( persistenceTransaction ) ;
785
800
} )
786
801
. next ( holdsPrimaryLease => {
787
802
if ( ! holdsPrimaryLease ) {
@@ -800,14 +815,14 @@ export class IndexedDbPersistence implements Persistence {
800
815
return transactionOperation ( persistenceTransaction ) ;
801
816
} )
802
817
. next ( result => {
803
- return this . acquireOrExtendPrimaryLease ( simpleDbTxn ) . next (
804
- ( ) => result
805
- ) ;
818
+ return this . acquireOrExtendPrimaryLease (
819
+ persistenceTransaction
820
+ ) . next ( ( ) => result ) ;
806
821
} ) ;
807
822
} else {
808
- return this . verifyAllowTabSynchronization ( simpleDbTxn ) . next ( ( ) =>
809
- transactionOperation ( persistenceTransaction )
810
- ) ;
823
+ return this . verifyAllowTabSynchronization (
824
+ persistenceTransaction
825
+ ) . next ( ( ) => transactionOperation ( persistenceTransaction ) ) ;
811
826
}
812
827
} )
813
828
. then ( result => {
@@ -823,7 +838,7 @@ export class IndexedDbPersistence implements Persistence {
823
838
// TODO(b/114226234): Remove this check when `synchronizeTabs` can no longer
824
839
// be turned off.
825
840
private verifyAllowTabSynchronization (
826
- txn : SimpleDbTransaction
841
+ txn : PersistenceTransaction
827
842
) : PersistencePromise < void > {
828
843
const store = primaryClientStore ( txn ) ;
829
844
return store . get ( DbPrimaryClient . key ) . next ( currentPrimary => {
@@ -836,7 +851,10 @@ export class IndexedDbPersistence implements Persistence {
836
851
! this . isClientZombied ( currentPrimary . ownerId ) ;
837
852
838
853
if ( currentLeaseIsValid && ! this . isLocalClient ( currentPrimary ) ) {
839
- if ( ! currentPrimary ! . allowTabSynchronization ) {
854
+ if (
855
+ ! this . allowTabSynchronization ||
856
+ ! currentPrimary ! . allowTabSynchronization
857
+ ) {
840
858
throw new FirestoreError (
841
859
Code . FAILED_PRECONDITION ,
842
860
PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG
@@ -851,7 +869,7 @@ export class IndexedDbPersistence implements Persistence {
851
869
* method does not verify that the client is eligible for this lease.
852
870
*/
853
871
private acquireOrExtendPrimaryLease (
854
- txn : SimpleDbTransaction
872
+ txn : PersistenceTransaction
855
873
) : PersistencePromise < void > {
856
874
const newPrimary = new DbPrimaryClient (
857
875
this . clientId ,
@@ -887,7 +905,7 @@ export class IndexedDbPersistence implements Persistence {
887
905
888
906
/** Checks the primary lease and removes it if we are the current primary. */
889
907
private releasePrimaryLeaseIfHeld (
890
- txn : SimpleDbTransaction
908
+ txn : PersistenceTransaction
891
909
) : PersistencePromise < void > {
892
910
const store = primaryClientStore ( txn ) ;
893
911
return store . get ( DbPrimaryClient . key ) . next ( primaryClient => {
@@ -1052,18 +1070,22 @@ export class IndexedDbPersistence implements Persistence {
1052
1070
* Helper to get a typed SimpleDbStore for the primary client object store.
1053
1071
*/
1054
1072
function primaryClientStore (
1055
- txn : SimpleDbTransaction
1073
+ txn : PersistenceTransaction
1056
1074
) : SimpleDbStore < DbPrimaryClientKey , DbPrimaryClient > {
1057
- return txn . store < DbPrimaryClientKey , DbPrimaryClient > ( DbPrimaryClient . store ) ;
1075
+ return IndexedDbPersistence . getStore < DbPrimaryClientKey , DbPrimaryClient > (
1076
+ txn ,
1077
+ DbPrimaryClient . store
1078
+ ) ;
1058
1079
}
1059
1080
1060
1081
/**
1061
1082
* Helper to get a typed SimpleDbStore for the client metadata object store.
1062
1083
*/
1063
1084
function clientMetadataStore (
1064
- txn : SimpleDbTransaction
1085
+ txn : PersistenceTransaction
1065
1086
) : SimpleDbStore < DbClientMetadataKey , DbClientMetadata > {
1066
- return txn . store < DbClientMetadataKey , DbClientMetadata > (
1087
+ return IndexedDbPersistence . getStore < DbClientMetadataKey , DbClientMetadata > (
1088
+ txn ,
1067
1089
DbClientMetadata . store
1068
1090
) ;
1069
1091
}
0 commit comments