@@ -20,7 +20,6 @@ import { debugAssert } from '../util/assert';
2020import { Code , FirestoreError } from '../util/error' ;
2121import { logDebug , logError } from '../util/log' ;
2222import { Deferred } from '../util/promise' ;
23- import { SCHEMA_VERSION } from './indexeddb_schema' ;
2423import { PersistencePromise } from './persistence_promise' ;
2524
2625// References to `window` are guarded by SimpleDb.isAvailable()
@@ -54,88 +53,8 @@ export interface SimpleDbSchemaConverter {
5453 * See PersistencePromise for more details.
5554 */
5655export 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 ;
13958
14059 /** Deletes the specified database. */
14160 static delete ( name : string ) : Promise < void > {
@@ -233,7 +152,25 @@ export class SimpleDb {
233152 return Number ( version ) ;
234153 }
235154
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+
237174 const iOSVersion = SimpleDb . getIOSVersion ( getUA ( ) ) ;
238175 // NOTE: According to https://bugs.webkit.org/show_bug.cgi?id=197050, the
239176 // bug we're checking for should exist in iOS >= 12.2 and < 13, but for
@@ -249,12 +186,91 @@ export class SimpleDb {
249186 }
250187 }
251188
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+
252265 setVersionChangeListener (
253266 versionChangeListener : ( event : IDBVersionChangeEvent ) => void
254267 ) : 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+ }
258274 }
259275
260276 async runTransaction < T > (
@@ -268,12 +284,14 @@ export class SimpleDb {
268284 while ( true ) {
269285 ++ attemptNumber ;
270286
271- const transaction = SimpleDbTransaction . open (
272- this . db ,
273- readonly ? 'readonly' : 'readwrite' ,
274- objectStores
275- ) ;
276287 try {
288+ this . db = await this . ensureDb ( ) ;
289+
290+ const transaction = SimpleDbTransaction . open (
291+ this . db ,
292+ readonly ? 'readonly' : 'readwrite' ,
293+ objectStores
294+ ) ;
277295 const transactionFnResult = transactionFn ( transaction )
278296 . catch ( error => {
279297 // Abort the transaction if there was an error.
@@ -312,6 +330,8 @@ export class SimpleDb {
312330 retryable
313331 ) ;
314332
333+ this . close ( ) ;
334+
315335 if ( ! retryable ) {
316336 return Promise . reject ( error ) ;
317337 }
@@ -320,7 +340,10 @@ export class SimpleDb {
320340 }
321341
322342 close ( ) : void {
323- this . db . close ( ) ;
343+ if ( this . db ) {
344+ this . db . close ( ) ;
345+ }
346+ this . db = undefined ;
324347 }
325348}
326349
@@ -400,7 +423,7 @@ export interface IterateOptions {
400423export class IndexedDbTransactionError extends FirestoreError {
401424 name = 'IndexedDbTransactionError' ;
402425
403- constructor ( cause : Error ) {
426+ constructor ( cause : Error | string ) {
404427 super ( Code . UNAVAILABLE , 'IndexedDB transaction failed: ' + cause ) ;
405428 }
406429}
@@ -429,7 +452,11 @@ export class SimpleDbTransaction {
429452 mode : IDBTransactionMode ,
430453 objectStoreNames : string [ ]
431454 ) : 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+ }
433460 }
434461
435462 constructor ( private readonly transaction : IDBTransaction ) {
0 commit comments