Skip to content

Commit c2b215c

Browse files
authored
[auth] Retry IndexedDB errors a fixed number of times (#4059)
There appears to be an issue with webkit where it periodically closes the connections such as when the tab is backgrounded (https://bugs.webkit.org/show_bug.cgi?id=197050). We can't repro, but Firestore did something similar and have stopped getting error reports.
1 parent d2adf4e commit c2b215c

File tree

3 files changed

+223
-127
lines changed

3 files changed

+223
-127
lines changed

.changeset/gentle-gifts-sort.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/auth': patch
3+
---
4+
5+
Retry IndexedDB errors a fixed number of times to handle connection issues in mobile webkit.

packages/auth/src/storage/indexeddb.js

+89-53
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @license
3-
* Copyright 2017 Google Inc.
3+
* Copyright 2017 Google LLC
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -226,6 +226,14 @@ fireauth.storage.IndexedDB.VERSION_ = 1;
226226
fireauth.storage.IndexedDB.POLLING_DELAY_ = 800;
227227

228228

229+
/**
230+
* Maximum number of times to retry a transaction in the event the connection is
231+
* closed.
232+
* @private @const {number}
233+
*/
234+
fireauth.storage.IndexedDB.TRANSACTION_RETRY_COUNT_ = 3;
235+
236+
229237
/**
230238
* The indexedDB polling stop error.
231239
* @private @const {string}
@@ -341,6 +349,37 @@ fireauth.storage.IndexedDB.prototype.initializeDbAndRun_ =
341349
return this.initPromise_;
342350
};
343351

352+
/**
353+
* Attempts to run a transaction, in the event of an error will re-initialize
354+
* the DB connection and retry a fixed number of times.
355+
* @param {function(!IDBDatabase): !goog.Promise<{T}>} transaction A method
356+
* which performs a transactional operation on an IDBDatabase.
357+
* @template T
358+
* @return {!goog.Promise<T>}
359+
* @private
360+
*/
361+
fireauth.storage.IndexedDB.prototype.withRetry_ = function(transaction) {
362+
let numAttempts = 0;
363+
const attempt = (resolve, reject) => {
364+
this.initializeDbAndRun_()
365+
.then(transaction)
366+
.then(resolve)
367+
.thenCatch((error) => {
368+
if (++numAttempts >
369+
fireauth.storage.IndexedDB.TRANSACTION_RETRY_COUNT_) {
370+
reject(error);
371+
return;
372+
}
373+
return this.initializeDbAndRun_().then((db) => {
374+
db.close();
375+
this.initPromise_ = undefined;
376+
return attempt(resolve, reject);
377+
});
378+
});
379+
};
380+
return new goog.Promise(attempt);
381+
}
382+
344383

345384
/**
346385
* @return {boolean} Whether indexedDB is available or not.
@@ -416,43 +455,42 @@ fireauth.storage.IndexedDB.prototype.onIDBRequest_ =
416455
* @override
417456
*/
418457
fireauth.storage.IndexedDB.prototype.set = function(key, value) {
419-
var isLocked = false;
420-
var dbTemp;
421-
var self = this;
422-
return this.initializeDbAndRun_()
423-
.then(function(db) {
424-
dbTemp = db;
425-
var objectStore = self.getDataObjectStore_(
426-
self.getTransaction_(dbTemp, true));
427-
return self.onIDBRequest_(objectStore.get(key));
428-
})
429-
.then(function(data) {
430-
var objectStore = self.getDataObjectStore_(
431-
self.getTransaction_(dbTemp, true));
458+
let isLocked = false;
459+
return this
460+
.withRetry_((db) => {
461+
const objectStore =
462+
this.getDataObjectStore_(this.getTransaction_(db, true));
463+
return this.onIDBRequest_(objectStore.get(key));
464+
})
465+
.then((data) => {
466+
return this.withRetry_((db) => {
467+
const objectStore =
468+
this.getDataObjectStore_(this.getTransaction_(db, true));
432469
if (data) {
433470
// Update the value(s) in the object that you want to change
434471
data.value = value;
435472
// Put this updated object back into the database.
436-
return self.onIDBRequest_(objectStore.put(data));
473+
return this.onIDBRequest_(objectStore.put(data));
437474
}
438-
self.pendingOpsTracker_++;
475+
this.pendingOpsTracker_++;
439476
isLocked = true;
440-
var obj = {};
441-
obj[self.dataKeyPath_] = key;
442-
obj[self.valueKeyPath_] = value;
443-
return self.onIDBRequest_(objectStore.add(obj));
444-
})
445-
.then(function() {
446-
// Save in local copy to avoid triggering false external event.
447-
self.localMap_[key] = value;
448-
// Announce change in key to service worker.
449-
return self.notifySW_(key);
450-
})
451-
.thenAlways(function() {
452-
if (isLocked) {
453-
self.pendingOpsTracker_--;
454-
}
477+
const obj = {};
478+
obj[this.dataKeyPath_] = key;
479+
obj[this.valueKeyPath_] = value;
480+
return this.onIDBRequest_(objectStore.add(obj));
455481
});
482+
})
483+
.then(() => {
484+
// Save in local copy to avoid triggering false external event.
485+
this.localMap_[key] = value;
486+
// Announce change in key to service worker.
487+
return this.notifySW_(key);
488+
})
489+
.thenAlways(() => {
490+
if (isLocked) {
491+
this.pendingOpsTracker_--;
492+
}
493+
});
456494
};
457495

458496

@@ -496,15 +534,14 @@ fireauth.storage.IndexedDB.prototype.notifySW_ = function(key) {
496534
* @override
497535
*/
498536
fireauth.storage.IndexedDB.prototype.get = function(key) {
499-
var self = this;
500-
return this.initializeDbAndRun_()
501-
.then(function(db) {
502-
return self.onIDBRequest_(
503-
self.getDataObjectStore_(self.getTransaction_(db, false)).get(key));
504-
})
505-
.then(function(response) {
506-
return response && response.value;
507-
});
537+
return this
538+
.withRetry_((db) => {
539+
return this.onIDBRequest_(
540+
this.getDataObjectStore_(this.getTransaction_(db, false)).get(key));
541+
})
542+
.then((response) => {
543+
return response && response.value;
544+
});
508545
};
509546

510547

@@ -516,23 +553,22 @@ fireauth.storage.IndexedDB.prototype.get = function(key) {
516553
* @override
517554
*/
518555
fireauth.storage.IndexedDB.prototype.remove = function(key) {
519-
var isLocked = false;
520-
var self = this;
521-
return this.initializeDbAndRun_()
522-
.then(function(db) {
556+
let isLocked = false;
557+
return this
558+
.withRetry_((db) => {
523559
isLocked = true;
524-
self.pendingOpsTracker_++;
525-
return self.onIDBRequest_(
526-
self.getDataObjectStore_(
527-
self.getTransaction_(db, true))['delete'](key));
528-
}).then(function() {
560+
this.pendingOpsTracker_++;
561+
return this.onIDBRequest_(
562+
this.getDataObjectStore_(
563+
this.getTransaction_(db, true))['delete'](key));
564+
}).then(() => {
529565
// Delete from local copy to avoid triggering false external event.
530-
delete self.localMap_[key];
566+
delete this.localMap_[key];
531567
// Announce change in key to service worker.
532-
return self.notifySW_(key);
533-
}).thenAlways(function() {
568+
return this.notifySW_(key);
569+
}).thenAlways(() => {
534570
if (isLocked) {
535-
self.pendingOpsTracker_--;
571+
this.pendingOpsTracker_--;
536572
}
537573
});
538574
};

0 commit comments

Comments
 (0)