15
15
* limitations under the License.
16
16
*/
17
17
18
- import { getGlobal , getUA , isIndexedDBAvailable } from '@firebase/util' ;
18
+ import { getGlobal , getUA , isIndexedDBAvailable } from '@firebase/util' ;
19
19
20
- import { debugAssert } from '../util/assert' ;
21
- import { Code , FirestoreError } from '../util/error' ;
22
- import { logDebug , logError } from '../util/log' ;
23
- import { Deferred } from '../util/promise' ;
20
+ import { debugAssert } from '../util/assert' ;
21
+ import { Code , FirestoreError } from '../util/error' ;
22
+ import { logDebug , logError , logWarn } from '../util/log' ;
23
+ import { Deferred } from '../util/promise' ;
24
24
25
- import { PersistencePromise } from './persistence_promise' ;
25
+ import { PersistencePromise } from './persistence_promise' ;
26
+ import {
27
+ type DatabaseDeletedListener ,
28
+ DatabaseDeletedListenerContinueResult
29
+ } from './persistence' ;
26
30
27
31
// References to `indexedDB` are guarded by SimpleDb.isAvailable() and getGlobal()
28
32
/* eslint-disable no-restricted-globals */
@@ -159,7 +163,7 @@ export class SimpleDbTransaction {
159
163
export class SimpleDb {
160
164
private db ?: IDBDatabase ;
161
165
private lastClosedDbVersion : number | null = null ;
162
- private versionchangelistener ?: ( event : IDBVersionChangeEvent ) => void ;
166
+ private databaseDeletedListener ?: DatabaseDeletedListener ;
163
167
164
168
/** Deletes the specified database. */
165
169
static delete ( name : string ) : Promise < void > {
@@ -352,19 +356,35 @@ export class SimpleDb {
352
356
this . lastClosedDbVersion !== null &&
353
357
this . lastClosedDbVersion !== event . oldVersion
354
358
) {
355
- // This thrown error will get passed to the `onerror` callback
356
- // registered above, and will then be propagated correctly.
357
- throw new Error (
358
- `refusing to open IndexedDB database due to potential ` +
359
- `corruption of the IndexedDB database data; this corruption ` +
360
- `could be caused by clicking the "clear site data" button in ` +
361
- `a web browser; try reloading the web page to re-initialize ` +
362
- `the IndexedDB database: ` +
363
- `lastClosedDbVersion=${ this . lastClosedDbVersion } , ` +
364
- `event.oldVersion=${ event . oldVersion } , ` +
365
- `event.newVersion=${ event . newVersion } , ` +
366
- `db.version= ${ db . version } `
359
+ logWarn (
360
+ `IndexedDB onupgradeneeded indicates that the ` +
361
+ `database contents may have been cleared, such as by clicking ` +
362
+ `the "clear site data" button in a browser. This _could_ cause ` +
363
+ `corruption of the IndexeDB database data if the clear ` +
364
+ `operation happened in the middle of Firestore operations. ( ` +
365
+ `db.name= ${ db . name } , ` +
366
+ `db.version= ${ db . version } , ` +
367
+ `lastClosedDbVersion=${ this . lastClosedDbVersion } , ` +
368
+ `event.oldVersion=${ event . oldVersion } , ` +
369
+ `event.newVersion=${ event . newVersion } ` +
370
+ `) `
367
371
) ;
372
+ if ( this . databaseDeletedListener ) {
373
+ const listenerResult = this . databaseDeletedListener ( "site data cleared" ) ;
374
+ if ( listenerResult !== DatabaseDeletedListenerContinueResult ) {
375
+ throw new Error (
376
+ `Refusing to open IndexedDB database after having been ` +
377
+ `cleared, such as by clicking the "clear site data" button ` +
378
+ `in a web browser: ${ listenerResult . reason } (` +
379
+ `db.name=${ db . name } , ` +
380
+ `db.version=${ db . version } , ` +
381
+ `lastClosedDbVersion=${ this . lastClosedDbVersion } , ` +
382
+ `event.oldVersion=${ event . oldVersion } , ` +
383
+ `event.newVersion=${ event . newVersion } ` +
384
+ `)`
385
+ ) ;
386
+ }
387
+ }
368
388
}
369
389
this . schemaConverter
370
390
. createOrUpgrade (
@@ -387,27 +407,56 @@ export class SimpleDb {
387
407
event => {
388
408
const db = event . target as IDBDatabase ;
389
409
this . lastClosedDbVersion = db . version ;
410
+ logWarn (
411
+ `IndexedDB "close" event received, indicating abnormal database ` +
412
+ `closure. The database contents may have been cleared, such as ` +
413
+ `by clicking the "clear site data" button in a browser. ` +
414
+ `Re-opening the IndexedDB database may fail to avoid IndexedDB ` +
415
+ `database data corruption (` +
416
+ `db.name=${ db . name } , ` +
417
+ `db.version=${ db . version } ` +
418
+ `)`
419
+ ) ;
390
420
} ,
391
421
{ passive : true }
392
422
) ;
393
423
}
394
424
395
- if ( this . versionchangelistener ) {
396
- this . db . onversionchange = event => this . versionchangelistener ! ( event ) ;
397
- }
425
+ this . db . addEventListener ( "versionchange" , event => {
426
+ const db = event . target as IDBDatabase ;
427
+ if ( event . newVersion !== null ) {
428
+ return ;
429
+ }
430
+
431
+ logDebug (
432
+ `IndexedDB "versionchange" event with newVersion===null received; ` +
433
+ `this is likely because clearIndexedDbPersistence() was called, ` +
434
+ `possibly in another tab if multi-tab persistence is enabled.`
435
+ ) ;
436
+ if ( this . databaseDeletedListener ) {
437
+ const listenerResult = this . databaseDeletedListener ( "persistence cleared" ) ;
438
+ if ( listenerResult !== DatabaseDeletedListenerContinueResult ) {
439
+ logWarn (
440
+ `Closing IndexedDB database "${ db . name } " in response to ` +
441
+ `"versionchange" event with newVersion===null: ` +
442
+ `${ listenerResult . reason } `
443
+ ) ;
444
+ db . close ( ) ;
445
+ if ( db === this . db ) {
446
+ this . db = undefined ;
447
+ }
448
+ }
449
+ }
450
+ } , { passive :true } ) ;
398
451
399
452
return this . db ;
400
453
}
401
454
402
- setVersionChangeListener (
403
- versionChangeListener : ( event : IDBVersionChangeEvent ) => void
404
- ) : void {
405
- this . versionchangelistener = versionChangeListener ;
406
- if ( this . db ) {
407
- this . db . onversionchange = ( event : IDBVersionChangeEvent ) => {
408
- return versionChangeListener ( event ) ;
409
- } ;
455
+ setDatabaseDeletedListener ( databaseDeletedListener : DatabaseDeletedListener ) : void {
456
+ if ( this . databaseDeletedListener ) {
457
+ throw new Error ( "setOnDatabaseDeletedListener() has already been called" ) ;
410
458
}
459
+ this . databaseDeletedListener = databaseDeletedListener ;
411
460
}
412
461
413
462
async runTransaction < T > (
0 commit comments