@@ -20,7 +20,6 @@ import { debugAssert } from '../util/assert';
20
20
import { Code , FirestoreError } from '../util/error' ;
21
21
import { logDebug , logError } from '../util/log' ;
22
22
import { Deferred } from '../util/promise' ;
23
- import { SCHEMA_VERSION } from './indexeddb_schema' ;
24
23
import { PersistencePromise } from './persistence_promise' ;
25
24
26
25
// References to `window` are guarded by SimpleDb.isAvailable()
@@ -54,88 +53,8 @@ export interface SimpleDbSchemaConverter {
54
53
* See PersistencePromise for more details.
55
54
*/
56
55
export class SimpleDb {
57
- /**
58
- * Opens the specified database, creating or upgrading it if necessary.
59
- *
60
- * Note that `version` must not be a downgrade. IndexedDB does not support downgrading the schema
61
- * version. We currently do not support any way to do versioning outside of IndexedDB's versioning
62
- * mechanism, as only version-upgrade transactions are allowed to do things like create
63
- * objectstores.
64
- */
65
- static openOrCreate (
66
- name : string ,
67
- version : number ,
68
- schemaConverter : SimpleDbSchemaConverter
69
- ) : Promise < SimpleDb > {
70
- debugAssert (
71
- SimpleDb . isAvailable ( ) ,
72
- 'IndexedDB not supported in current environment.'
73
- ) ;
74
- logDebug ( LOG_TAG , 'Opening database:' , name ) ;
75
- return new PersistencePromise < SimpleDb > ( ( resolve , reject ) => {
76
- // TODO(mikelehen): Investigate browser compatibility.
77
- // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
78
- // suggests IE9 and older WebKit browsers handle upgrade
79
- // differently. They expect setVersion, as described here:
80
- // https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeRequest/setVersion
81
- const request = indexedDB . open ( name , version ) ;
82
-
83
- request . onsuccess = ( event : Event ) => {
84
- const db = ( event . target as IDBOpenDBRequest ) . result ;
85
- resolve ( new SimpleDb ( db ) ) ;
86
- } ;
87
-
88
- request . onblocked = ( ) => {
89
- reject (
90
- new FirestoreError (
91
- Code . FAILED_PRECONDITION ,
92
- 'Cannot upgrade IndexedDB schema while another tab is open. ' +
93
- 'Close all tabs that access Firestore and reload this page to proceed.'
94
- )
95
- ) ;
96
- } ;
97
-
98
- request . onerror = ( event : Event ) => {
99
- const error : DOMException = ( event . target as IDBOpenDBRequest ) . error ! ;
100
- if ( error . name === 'VersionError' ) {
101
- reject (
102
- new FirestoreError (
103
- Code . FAILED_PRECONDITION ,
104
- 'A newer version of the Firestore SDK was previously used and so the persisted ' +
105
- 'data is not compatible with the version of the SDK you are now using. The SDK ' +
106
- 'will operate with persistence disabled. If you need persistence, please ' +
107
- 're-upgrade to a newer version of the SDK or else clear the persisted IndexedDB ' +
108
- 'data for your app to start fresh.'
109
- )
110
- ) ;
111
- } else {
112
- reject ( error ) ;
113
- }
114
- } ;
115
-
116
- request . onupgradeneeded = ( event : IDBVersionChangeEvent ) => {
117
- logDebug (
118
- LOG_TAG ,
119
- 'Database "' + name + '" requires upgrade from version:' ,
120
- event . oldVersion
121
- ) ;
122
- const db = ( event . target as IDBOpenDBRequest ) . result ;
123
- schemaConverter
124
- . createOrUpgrade (
125
- db ,
126
- request . transaction ! ,
127
- event . oldVersion ,
128
- SCHEMA_VERSION
129
- )
130
- . next ( ( ) => {
131
- logDebug (
132
- LOG_TAG ,
133
- 'Database upgrade to version ' + SCHEMA_VERSION + ' complete'
134
- ) ;
135
- } ) ;
136
- } ;
137
- } ) . toPromise ( ) ;
138
- }
56
+ private db ?: IDBDatabase ;
57
+ private versionchangelistener ?: ( event : IDBVersionChangeEvent ) => void ;
139
58
140
59
/** Deletes the specified database. */
141
60
static delete ( name : string ) : Promise < void > {
@@ -233,7 +152,25 @@ export class SimpleDb {
233
152
return Number ( version ) ;
234
153
}
235
154
236
- constructor ( private db : IDBDatabase ) {
155
+ /*
156
+ * Creates a new SimpleDb wrapper for IndexedDb database `name`.
157
+ *
158
+ * Note that `version` must not be a downgrade. IndexedDB does not support
159
+ * downgrading the schema version. We currently do not support any way to do
160
+ * versioning outside of IndexedDB's versioning mechanism, as only
161
+ * version-upgrade transactions are allowed to do things like create
162
+ * objectstores.
163
+ */
164
+ constructor (
165
+ private readonly name : string ,
166
+ private readonly version : number ,
167
+ private readonly schemaConverter : SimpleDbSchemaConverter
168
+ ) {
169
+ debugAssert (
170
+ SimpleDb . isAvailable ( ) ,
171
+ 'IndexedDB not supported in current environment.'
172
+ ) ;
173
+
237
174
const iOSVersion = SimpleDb . getIOSVersion ( getUA ( ) ) ;
238
175
// NOTE: According to https://bugs.webkit.org/show_bug.cgi?id=197050, the
239
176
// bug we're checking for should exist in iOS >= 12.2 and < 13, but for
@@ -249,12 +186,91 @@ export class SimpleDb {
249
186
}
250
187
}
251
188
189
+ /**
190
+ * Opens the specified database, creating or upgrading it if necessary.
191
+ */
192
+ async ensureDb ( ) : Promise < IDBDatabase > {
193
+ if ( ! this . db ) {
194
+ logDebug ( LOG_TAG , 'Opening database:' , this . name ) ;
195
+ this . db = await new Promise < IDBDatabase > ( ( resolve , reject ) => {
196
+ // TODO(mikelehen): Investigate browser compatibility.
197
+ // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
198
+ // suggests IE9 and older WebKit browsers handle upgrade
199
+ // differently. They expect setVersion, as described here:
200
+ // https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeRequest/setVersion
201
+ const request = indexedDB . open ( this . name , this . version ) ;
202
+
203
+ request . onsuccess = ( event : Event ) => {
204
+ const db = ( event . target as IDBOpenDBRequest ) . result ;
205
+ resolve ( db ) ;
206
+ } ;
207
+
208
+ request . onblocked = ( ) => {
209
+ reject (
210
+ new IndexedDbTransactionError (
211
+ 'Cannot upgrade IndexedDB schema while another tab is open. ' +
212
+ 'Close all tabs that access Firestore and reload this page to proceed.'
213
+ )
214
+ ) ;
215
+ } ;
216
+
217
+ request . onerror = ( event : Event ) => {
218
+ const error : DOMException = ( event . target as IDBOpenDBRequest ) . error ! ;
219
+ if ( error . name === 'VersionError' ) {
220
+ reject (
221
+ new FirestoreError (
222
+ Code . FAILED_PRECONDITION ,
223
+ 'A newer version of the Firestore SDK was previously used and so the persisted ' +
224
+ 'data is not compatible with the version of the SDK you are now using. The SDK ' +
225
+ 'will operate with persistence disabled. If you need persistence, please ' +
226
+ 're-upgrade to a newer version of the SDK or else clear the persisted IndexedDB ' +
227
+ 'data for your app to start fresh.'
228
+ )
229
+ ) ;
230
+ } else {
231
+ reject ( new IndexedDbTransactionError ( error ) ) ;
232
+ }
233
+ } ;
234
+
235
+ request . onupgradeneeded = ( event : IDBVersionChangeEvent ) => {
236
+ logDebug (
237
+ LOG_TAG ,
238
+ 'Database "' + this . name + '" requires upgrade from version:' ,
239
+ event . oldVersion
240
+ ) ;
241
+ const db = ( event . target as IDBOpenDBRequest ) . result ;
242
+ this . schemaConverter
243
+ . createOrUpgrade (
244
+ db ,
245
+ request . transaction ! ,
246
+ event . oldVersion ,
247
+ this . version
248
+ )
249
+ . next ( ( ) => {
250
+ logDebug (
251
+ LOG_TAG ,
252
+ 'Database upgrade to version ' + this . version + ' complete'
253
+ ) ;
254
+ } ) ;
255
+ } ;
256
+ } ) ;
257
+ }
258
+
259
+ if ( this . versionchangelistener ) {
260
+ this . db . onversionchange = event => this . versionchangelistener ! ( event ) ;
261
+ }
262
+ return this . db ;
263
+ }
264
+
252
265
setVersionChangeListener (
253
266
versionChangeListener : ( event : IDBVersionChangeEvent ) => void
254
267
) : void {
255
- this . db . onversionchange = ( event : IDBVersionChangeEvent ) => {
256
- return versionChangeListener ( event ) ;
257
- } ;
268
+ this . versionchangelistener = versionChangeListener ;
269
+ if ( this . db ) {
270
+ this . db . onversionchange = ( event : IDBVersionChangeEvent ) => {
271
+ return versionChangeListener ( event ) ;
272
+ } ;
273
+ }
258
274
}
259
275
260
276
async runTransaction < T > (
@@ -268,12 +284,14 @@ export class SimpleDb {
268
284
while ( true ) {
269
285
++ attemptNumber ;
270
286
271
- const transaction = SimpleDbTransaction . open (
272
- this . db ,
273
- readonly ? 'readonly' : 'readwrite' ,
274
- objectStores
275
- ) ;
276
287
try {
288
+ this . db = await this . ensureDb ( ) ;
289
+
290
+ const transaction = SimpleDbTransaction . open (
291
+ this . db ,
292
+ readonly ? 'readonly' : 'readwrite' ,
293
+ objectStores
294
+ ) ;
277
295
const transactionFnResult = transactionFn ( transaction )
278
296
. catch ( error => {
279
297
// Abort the transaction if there was an error.
@@ -312,6 +330,8 @@ export class SimpleDb {
312
330
retryable
313
331
) ;
314
332
333
+ this . close ( ) ;
334
+
315
335
if ( ! retryable ) {
316
336
return Promise . reject ( error ) ;
317
337
}
@@ -320,7 +340,10 @@ export class SimpleDb {
320
340
}
321
341
322
342
close ( ) : void {
323
- this . db . close ( ) ;
343
+ if ( this . db ) {
344
+ this . db . close ( ) ;
345
+ }
346
+ this . db = undefined ;
324
347
}
325
348
}
326
349
@@ -400,7 +423,7 @@ export interface IterateOptions {
400
423
export class IndexedDbTransactionError extends FirestoreError {
401
424
name = 'IndexedDbTransactionError' ;
402
425
403
- constructor ( cause : Error ) {
426
+ constructor ( cause : Error | string ) {
404
427
super ( Code . UNAVAILABLE , 'IndexedDB transaction failed: ' + cause ) ;
405
428
}
406
429
}
@@ -429,7 +452,11 @@ export class SimpleDbTransaction {
429
452
mode : IDBTransactionMode ,
430
453
objectStoreNames : string [ ]
431
454
) : SimpleDbTransaction {
432
- return new SimpleDbTransaction ( db . transaction ( objectStoreNames , mode ) ) ;
455
+ try {
456
+ return new SimpleDbTransaction ( db . transaction ( objectStoreNames , mode ) ) ;
457
+ } catch ( e ) {
458
+ throw new IndexedDbTransactionError ( e ) ;
459
+ }
433
460
}
434
461
435
462
constructor ( private readonly transaction : IDBTransaction ) {
0 commit comments