@@ -19,9 +19,10 @@ import { getGlobal, getUA, isIndexedDBAvailable } from '@firebase/util';
19
19
20
20
import { debugAssert } from '../util/assert' ;
21
21
import { Code , FirestoreError } from '../util/error' ;
22
- import { logDebug , logError } from '../util/log' ;
22
+ import { logDebug , logError , logWarn } from '../util/log' ;
23
23
import { Deferred } from '../util/promise' ;
24
24
25
+ import { type DatabaseDeletedListener } from './persistence' ;
25
26
import { PersistencePromise } from './persistence_promise' ;
26
27
27
28
// References to `indexedDB` are guarded by SimpleDb.isAvailable() and getGlobal()
@@ -159,7 +160,7 @@ export class SimpleDbTransaction {
159
160
export class SimpleDb {
160
161
private db ?: IDBDatabase ;
161
162
private lastClosedDbVersion : number | null = null ;
162
- private versionchangelistener ?: ( event : IDBVersionChangeEvent ) => void ;
163
+ private databaseDeletedListener ?: DatabaseDeletedListener ;
163
164
164
165
/** Deletes the specified database. */
165
166
static delete ( name : string ) : Promise < void > {
@@ -352,19 +353,36 @@ export class SimpleDb {
352
353
this . lastClosedDbVersion !== null &&
353
354
this . lastClosedDbVersion !== event . oldVersion
354
355
) {
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: ` +
356
+ logWarn (
357
+ `IndexedDB onupgradeneeded indicates that the ` +
358
+ `database contents may have been cleared, such as by clicking ` +
359
+ `the "clear site data" button in a browser. This _could_ cause ` +
360
+ `corruption of the IndexeDB database data if the clear ` +
361
+ `operation happened in the middle of Firestore operations. ( ` +
362
+ `db.name= ${ db . name } , ` +
363
+ `db.version= ${ db . version } , ` +
363
364
`lastClosedDbVersion=${ this . lastClosedDbVersion } , ` +
364
365
`event.oldVersion=${ event . oldVersion } , ` +
365
- `event.newVersion=${ event . newVersion } , ` +
366
- `db.version= ${ db . version } `
366
+ `event.newVersion=${ event . newVersion } ` +
367
+ `) `
367
368
) ;
369
+ if ( this . databaseDeletedListener ) {
370
+ const listenerResult =
371
+ this . databaseDeletedListener ( 'site data cleared' ) ;
372
+ if ( listenerResult . type !== 'continue' ) {
373
+ throw new Error (
374
+ `Refusing to open IndexedDB database after having been ` +
375
+ `cleared, such as by clicking the "clear site data" button ` +
376
+ `in a web browser: ${ listenerResult . abortReason } (` +
377
+ `db.name=${ db . name } , ` +
378
+ `db.version=${ db . version } , ` +
379
+ `lastClosedDbVersion=${ this . lastClosedDbVersion } , ` +
380
+ `event.oldVersion=${ event . oldVersion } , ` +
381
+ `event.newVersion=${ event . newVersion } ` +
382
+ `)`
383
+ ) ;
384
+ }
385
+ }
368
386
}
369
387
this . schemaConverter
370
388
. createOrUpgrade (
@@ -387,27 +405,64 @@ export class SimpleDb {
387
405
event => {
388
406
const db = event . target as IDBDatabase ;
389
407
this . lastClosedDbVersion = db . version ;
408
+ logWarn (
409
+ `IndexedDB "close" event received, indicating abnormal database ` +
410
+ `closure. The database contents may have been cleared, such as ` +
411
+ `by clicking the "clear site data" button in a browser. ` +
412
+ `Re-opening the IndexedDB database may fail to avoid IndexedDB ` +
413
+ `database data corruption (` +
414
+ `db.name=${ db . name } , ` +
415
+ `db.version=${ db . version } ` +
416
+ `)`
417
+ ) ;
390
418
} ,
391
419
{ passive : true }
392
420
) ;
393
421
}
394
422
395
- if ( this . versionchangelistener ) {
396
- this . db . onversionchange = event => this . versionchangelistener ! ( event ) ;
397
- }
423
+ this . db . addEventListener (
424
+ 'versionchange' ,
425
+ 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 (
438
+ 'persistence cleared'
439
+ ) ;
440
+ if ( listenerResult . type !== 'continue' ) {
441
+ logWarn (
442
+ `Closing IndexedDB database "${ db . name } " in response to ` +
443
+ `"versionchange" event with newVersion===null: ` +
444
+ `${ listenerResult . abortReason } `
445
+ ) ;
446
+ db . close ( ) ;
447
+ if ( db === this . db ) {
448
+ this . db = undefined ;
449
+ }
450
+ }
451
+ }
452
+ } ,
453
+ { passive : true }
454
+ ) ;
398
455
399
456
return this . db ;
400
457
}
401
458
402
- setVersionChangeListener (
403
- versionChangeListener : ( event : IDBVersionChangeEvent ) => void
459
+ setDatabaseDeletedListener (
460
+ databaseDeletedListener : DatabaseDeletedListener
404
461
) : void {
405
- this . versionchangelistener = versionChangeListener ;
406
- if ( this . db ) {
407
- this . db . onversionchange = ( event : IDBVersionChangeEvent ) => {
408
- return versionChangeListener ( event ) ;
409
- } ;
462
+ if ( this . databaseDeletedListener ) {
463
+ throw new Error ( 'setOnDatabaseDeletedListener() has already been called' ) ;
410
464
}
465
+ this . databaseDeletedListener = databaseDeletedListener ;
411
466
}
412
467
413
468
async runTransaction < T > (
0 commit comments