IndexedDB is a large-scale, NoSQL storage system. It lets you store almost anything in the user's browser (google uses this technology inside google docs extensively).
But with great power comes great responsibility. When working with traditional ( server side ) databases you are in control of the database
schema and deploying new versions or rolling back to previous ones.
However, when the database is in client control, doing those things reliably is not easy.
The Issue
The issue that I'm going to address in this blog post is about the case when the user has multiple tabs (or windows ) open of the same site. Since all tabs are using the same underlying database, there is a possibility that a user when opening the new tab of the same site receives the new version of the application (just pushed to production) and that version brings changes to the database schema.
When that happens new tab has a newer version of the database and code running in that tab (since this is the newest version of the application ) is made to work with the new DB schema, however in the previous tab old version of the code works with different DB schema
and if we leave that code running it can corrupt the database.
The Solution
Luckily creators of the indexedDB have foreseen this problem early in the design phase of the technology itself and they have given us some tools to handle such conflicts.
There are two events that we can listen to when we connect to the database:
- The
onversionchange
event handler handles the version change event, fired when a database structure change was requested elsewhere ( new tab/ window) - The
onblocked
event handles the scenario when we try to upgrade the database (change to the new schema) but the database is still in use somewhere (another tab/window) even after theonversionchange
event is sent.
I'm going to use these two events to safely upgrade the database.
These are the required steps:
- Deploy the new version of the database ( increase the
version
number) - Close the database connection in tab 1 by listening to
onversionchange
- Force the user to reload tab 1 by creating the overlay and disabling any interaction with the site.
Here is how it looks with three chromium windows on the same site:
The Code
//database version starts at 1
const version = 1
const dbName = "testing"
let db = null
// open the database connection
let openRequest = indexedDB.open(dbName, version)
openRequest.onsuccess = function(event) {
// store the result of opening the database connection in the db variable.
db = openRequest.result
// add listener to handle database version change
db.onversionchange = function() {
//!important when version change is detected close the database immediately
db.close() // hint:1
// instruct the user to reload the page via popup
// and block the interaction
launchPopup($newVersionPopupContent)
}
}
To trigger the onversionchange
event listener all we need to do is increase the version
variable that is passed to the indexedDB.open
method.
Code at hint:1
( db.close
) is very important, if we do not close the database connection when the onversionchange
event is triggered new code ( tab 2 ) will also be blocked.
Remember, onversionchange
is not triggered in the new tab ( new db - tab 2) this event will be triggered in all tabs with the old db.
Concluson
This is my solution to safely handle IndexedDB upgrade conflict. Some people might not like forcing the user to reload the page but that is the safest way, and it is highly unlikely that the user will be in the situation when two different application versions are running in the browser in the first place, but hey it can happen. It happened to me. So just make them reload.
I have made the demo code available via github.
If you want to learn more about IndexedDB Google web developer portal has a great intro to the subject.
Top comments (2)
@ivandotv - do you know if this works even if the other tabs were offline at the time? (I would imagine it does, but haven't had a chance to test yet)
I believe that is should work correctly.