@@ -54,88 +54,8 @@ export interface SimpleDbSchemaConverter {
5454 * See PersistencePromise for more details.
5555 */
5656export 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- }
57+ private db ?: IDBDatabase ;
58+ private versionchangelistener ?: ( event : IDBVersionChangeEvent ) => void ;
13959
14060 /** Deletes the specified database. */
14161 static delete ( name : string ) : Promise < void > {
@@ -233,7 +153,25 @@ export class SimpleDb {
233153 return Number ( version ) ;
234154 }
235155
236- constructor ( private db : IDBDatabase ) {
156+ /*
157+ * Creates a new SimpleDb wrapper for IndexedDb database `name`.
158+ *
159+ * Note that `version` must not be a downgrade. IndexedDB does not support
160+ * downgrading the schema version. We currently do not support any way to do
161+ * versioning outside of IndexedDB's versioning mechanism, as only
162+ * version-upgrade transactions are allowed to do things like create
163+ * objectstores.
164+ */
165+ constructor (
166+ private readonly name : string ,
167+ private readonly version : number ,
168+ private readonly schemaConverter : SimpleDbSchemaConverter
169+ ) {
170+ debugAssert (
171+ SimpleDb . isAvailable ( ) ,
172+ 'IndexedDB not supported in current environment.'
173+ ) ;
174+
237175 const iOSVersion = SimpleDb . getIOSVersion ( getUA ( ) ) ;
238176 // NOTE: According to https://bugs.webkit.org/show_bug.cgi?id=197050, the
239177 // bug we're checking for should exist in iOS >= 12.2 and < 13, but for
@@ -249,12 +187,91 @@ export class SimpleDb {
249187 }
250188 }
251189
190+ /**
191+ * Opens the specified database, creating or upgrading it if necessary.
192+ */
193+ async ensureDb ( ) : Promise < IDBDatabase > {
194+ if ( ! this . db ) {
195+ logDebug ( LOG_TAG , 'Opening database:' , this . name ) ;
196+ this . db = await new Promise < IDBDatabase > ( ( resolve , reject ) => {
197+ // TODO(mikelehen): Investigate browser compatibility.
198+ // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
199+ // suggests IE9 and older WebKit browsers handle upgrade
200+ // differently. They expect setVersion, as described here:
201+ // https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeRequest/setVersion
202+ const request = indexedDB . open ( this . name , this . version ) ;
203+
204+ request . onsuccess = ( event : Event ) => {
205+ const db = ( event . target as IDBOpenDBRequest ) . result ;
206+ resolve ( db ) ;
207+ } ;
208+
209+ request . onblocked = ( ) => {
210+ reject (
211+ new IndexedDbTransactionError (
212+ 'Cannot upgrade IndexedDB schema while another tab is open. ' +
213+ 'Close all tabs that access Firestore and reload this page to proceed.'
214+ )
215+ ) ;
216+ } ;
217+
218+ request . onerror = ( event : Event ) => {
219+ const error : DOMException = ( event . target as IDBOpenDBRequest ) . error ! ;
220+ if ( error . name === 'VersionError' ) {
221+ reject (
222+ new FirestoreError (
223+ Code . FAILED_PRECONDITION ,
224+ 'A newer version of the Firestore SDK was previously used and so the persisted ' +
225+ 'data is not compatible with the version of the SDK you are now using. The SDK ' +
226+ 'will operate with persistence disabled. If you need persistence, please ' +
227+ 're-upgrade to a newer version of the SDK or else clear the persisted IndexedDB ' +
228+ 'data for your app to start fresh.'
229+ )
230+ ) ;
231+ } else {
232+ reject ( new IndexedDbTransactionError ( error ) ) ;
233+ }
234+ } ;
235+
236+ request . onupgradeneeded = ( event : IDBVersionChangeEvent ) => {
237+ logDebug (
238+ LOG_TAG ,
239+ 'Database "' + this . name + '" requires upgrade from version:' ,
240+ event . oldVersion
241+ ) ;
242+ const db = ( event . target as IDBOpenDBRequest ) . result ;
243+ this . schemaConverter
244+ . createOrUpgrade (
245+ db ,
246+ request . transaction ! ,
247+ event . oldVersion ,
248+ this . version
249+ )
250+ . next ( ( ) => {
251+ logDebug (
252+ LOG_TAG ,
253+ 'Database upgrade to version ' + this . version + ' complete'
254+ ) ;
255+ } ) ;
256+ } ;
257+ } ) ;
258+ }
259+
260+ if ( this . versionchangelistener ) {
261+ this . db . onversionchange = event => this . versionchangelistener ! ( event ) ;
262+ }
263+ return this . db ;
264+ }
265+
252266 setVersionChangeListener (
253267 versionChangeListener : ( event : IDBVersionChangeEvent ) => void
254268 ) : void {
255- this . db . onversionchange = ( event : IDBVersionChangeEvent ) => {
256- return versionChangeListener ( event ) ;
257- } ;
269+ this . versionchangelistener = versionChangeListener ;
270+ if ( this . db ) {
271+ this . db . onversionchange = ( event : IDBVersionChangeEvent ) => {
272+ return versionChangeListener ( event ) ;
273+ } ;
274+ }
258275 }
259276
260277 async runTransaction < T > (
@@ -268,12 +285,14 @@ export class SimpleDb {
268285 while ( true ) {
269286 ++ attemptNumber ;
270287
271- const transaction = SimpleDbTransaction . open (
272- this . db ,
273- readonly ? 'readonly' : 'readwrite' ,
274- objectStores
275- ) ;
276288 try {
289+ this . db = await this . ensureDb ( ) ;
290+
291+ const transaction = SimpleDbTransaction . open (
292+ this . db ,
293+ readonly ? 'readonly' : 'readwrite' ,
294+ objectStores
295+ ) ;
277296 const transactionFnResult = transactionFn ( transaction )
278297 . catch ( error => {
279298 // Abort the transaction if there was an error.
@@ -312,6 +331,8 @@ export class SimpleDb {
312331 retryable
313332 ) ;
314333
334+ this . close ( ) ;
335+
315336 if ( ! retryable ) {
316337 return Promise . reject ( error ) ;
317338 }
@@ -320,7 +341,10 @@ export class SimpleDb {
320341 }
321342
322343 close ( ) : void {
323- this . db . close ( ) ;
344+ if ( this . db ) {
345+ this . db . close ( ) ;
346+ }
347+ this . db = undefined ;
324348 }
325349}
326350
@@ -400,7 +424,7 @@ export interface IterateOptions {
400424export class IndexedDbTransactionError extends FirestoreError {
401425 name = 'IndexedDbTransactionError' ;
402426
403- constructor ( cause : Error ) {
427+ constructor ( cause : Error | string ) {
404428 super ( Code . UNAVAILABLE , 'IndexedDB transaction failed: ' + cause ) ;
405429 }
406430}
@@ -429,7 +453,11 @@ export class SimpleDbTransaction {
429453 mode : IDBTransactionMode ,
430454 objectStoreNames : string [ ]
431455 ) : SimpleDbTransaction {
432- return new SimpleDbTransaction ( db . transaction ( objectStoreNames , mode ) ) ;
456+ try {
457+ return new SimpleDbTransaction ( db . transaction ( objectStoreNames , mode ) ) ;
458+ } catch ( e ) {
459+ throw new IndexedDbTransactionError ( e ) ;
460+ }
433461 }
434462
435463 constructor ( private readonly transaction : IDBTransaction ) {
0 commit comments