Going Offline In An Online-Only Browser

Thank you to Carmen Bourlon for returning with a fabulous article on managing intermittent internet connection.

Creating a offline fallback for a legacy browser

What happens when we lose connection?

Before the world wide web was world wide, we all lived in a disconnected world. If you wanted to research something, you didn't pull out your phone and google it, you went to the library. If you wanted to learn something, you attended classes, you didn't look it up on YouTube. Along the way, this all changed.

The internet did a lot for bringing people together. It encouraged communication across vast differences, and offers education at a minimal cost. Ideas are shared more freely than ever before. But ... something interesting happened. Along the way, we forgot how to be disconnected. Today if you want to apply for a job, you must go online. If you want to go to school, you must go online. Everything is online, so what does that mean for someone who is offline?

Everyone will lose connection

Every user will lose connection at one point or another. It can be due to faulty equipment or the Internet Service Provider. The connection loss could even be from the application side, if the cloud service provider hosting the application has an outage. An intermittent connection can be caused by travelling, or even just walking to one side of a building with spotty service. Whether the loss of connection stems from a true loss of internet connection from the user, or if the application experiences an outage from a cloud service provider -- or even just a bad deploy -- the result is the same. A user who needed to use an application is no longer able.

A loss of connection can be caused by many things, but one thing is for sure: connection loss can and will happen to everyone.

Some are more likely to lose connection than others

Unfortunately some people who are more likely to lose connection than others. According to a 2018 Pew Research Study, nearly 20% of adults in the United States are smartphone dependent, meaning their smartphone is their primary, or only, way of accessing the internet. Some predictors of smartphone dependence include age, ethnicity, and income.

According to another Pew Research Study from 2015, smartphone-dependent individuals faced even more obstacles reaching the internet than simply not having internet access outside of their phone. Nearly half of respondents to the study reported stopping or canceling their phone plan due to cost, while a third of respondents reported hitting a data cap "frequently". This means that in addition to not having internet access outside of their phone, these individuals are very likely to lose what little access they have.

Fortunately, there is something that we, as developers, can do. We can design our apps in such a way to account for these difficulties and help bridge the gap between offline and online.

Typically developers will use a service worker to begin providing an offline experience. Unfortunately service workers are not available in legacy browsers, most notably Internet Explorer 11. A deprecated browser may seem unimportant, but according to CanIUse, more people are using Internet Explorer 11 than Microsoft Edge. And especially considering the demographics of those facing difficulties with Internet access -- namely income -- it's still important to support legacy browsers.

Fortunately there are a few tricks we can use to offer an offline experience to our legacy users, even without service workers.

Planning for connection loss

Even though we are working in a browser without service workers, planning for what happens when the user loses connection remains largely the same. It boils down to just a few questions.

  1. How will we indicate that the app has lost connection?

This is an important question to answer because it dictates a lot of how you'll handle the connection loss. If you don't need to save off data, you could do something as simple as an alert to let the user know the app is offline. If instead you need to store off data, this is a good opportunity to manage a user's expectations. You can show an alert letting them know their data is safely cached and what they should expect once the connection returns. You definitely don't want to give the appearance of normal functionality, only for your user's internet connection to return and they realize none of their work has actually been saved.

  1. Will the user be able to continue working?

Will we handle the connection loss similar to Google Docs where a user can continue working locally and sync work later? Will we offer a slimmed-down set of features? Will the app be completely unavailable and we simply let the user know that the app won't be available until their connection returns?

Once we know how the app will respond to losing connection, we can begin to plan around it. But the key is knowing that users will lose connection, and planning accordingly.

  1. Are we storing off data?

For the most part, it's kindest not to make your user enter data all over again just because their internet flickered. However, it will really depend on your app, and the scope of the data you want to cache. Fortunately caching options like Local Storage and IndexedDB are supported in nearly all browsers.

Offline Dino by  Sophia Heckman

Offline Dino by Sophia Heckman

Monitor internet connection

Monitoring a user's internet connection is actually quite simple, using navigator.onLine. This handy little built-in function returns a boolean based on whether or not the user has an internet connection. This makes it very simple to wrap any outgoing fetch or ajax requests in an if-statement before firing. This way, we never run an ajax request while the user doesn't have internet access.

Additionally, we can create event listeners to let us know immediately when a user loses connection and act on it right away.

    window.addEventListener('online', function(e) { /.../ });

    window.addEventListener('offline', function(e) { /.../ });

Note: Internet Explorer 11 does not support navigator.offLine, so if you want to manage a user's loss of connection, an event listener is the way to go.

Caching data

Whether or not to use Local Storage or IndexedDB really depends on your preferences and the complexity of the data you're caching. If it's only a few pieces of simple data like a few strings or integers, Local Storage may be the simplest option. Setting a value in Local Storage is quite simple. Let's say we want to cache an email address.

    localStorage.setItem('emailaddress', 'fake@fake.com');

And then to get the data back once we have a connection and we'll grab the data back out of Local Storage like so:

    localStorage.getItem('emailaddress');

IndexedDB has quite a bit more to it, but is also more robust and can store more data.

Note: If you're unfamiliar with IndexedDB, here is a blog post introducing it.

Assuming we've set up IndexedDB, pulling data out of it can look like this:

    db.transaction(objStore, "readwrite")
        .objectStore(objStore)
        .getAll()
        .onsuccess = function(event) {
          console.log(event.target.result);
        }

Unfortunately, Internet Explorer 11 and Microsoft Edge don't support some of the functions included in IndexedDB, including getAll(). One option we have is to open a cursor and pull out all data manually like so:

    var request = objectStore.openCursor();
    request.onsuccess = function(event) {
        var cursor = event.target.result
        if(cursor) {
            arr.push(cursor.value);
            cursor.continue();
        } else {
            // no more data!
        }
    }

Note: If you don't want to manually open cursors and manage searching yourself, here is a really good polyfill for getAll and some other functions for IndexedDB.

Caching pages

The last thing we'll look at is making sure the Response headers are taking advantage of the HTTP Cache. While legacy browsers don't support service workers, we can still accomplish caching by utilizing the Cache Control header. This particular header takes in a max-age directive which takes a value of seconds. The browser will read the value of the header and cache the files for that duration. For example:

    Cache-Control: max-age=31536000

This directs the browser to cache the response for one year. The browser will check its cache before a request is made, which has the added bonus of not going over the network and therefore speed up requests. If you're interested in learning more about Response Headers, this is a great place to start.

As you can see, making an app offline-friendly in a legacy browser isn't impossible at all. With a few tricks like feature detection and data caching, we can help our users move from online to offline, and back again, making for a kinder experience overall.

The contributors to JavaScript January are passionate engineers, designers and teachers. Emily Freeman is a developer advocate at Kickbox and curates the articles for JavaScript January.