We recently completed a sprint on a big feature set for one of our Angular.js projects: offline mode. The goal was to enable the user to perform as many actions offline as possible, all while maintaining a smooth, reliable experience with no data loss.
This is not a blog post about the general capabilities of offline mode in HTML5, nor a how-to guide for setting up offline mode in your app. (Here is a great resource for that, if that’s what you’re looking for.) This blog post will address the quirks and edge cases that came up in real life development. Hopefully they’ll save you some time with your projects!
What does “offline” mean anyway?
We define “offline” as the inability to connect to our API server, not just the state of lacking Internet. If our API is down then the application is not usable in its normal form, therefore we are “offline.” This makes the flow consistent whether you are developing locally, testing on a staging server, or using the site on production as an end user. (More on the quirks of local offline development later.)
Detecting when a user is “offline”
In theory, it’s easy to known when a user is online or offline. There’s an HTML5 property,
navigator.onLine, which is supposed to make it clear. But the problem is that browsers all treat this property differently. Chrome and Safari return
true if you’re connected to a local network, no matter if you have actual Internet access or not. Firefox and IE handle
navigator.onLine differently still.
So if you can’t trust
navigator.onLine, what do you use? We said that we treat “offline” as the inability to connect to our backend server, which standardizes “offline” across browsers. Specifically, we employ EventSource to maintain a connection to our server. Every 3 seconds the server emits a heartbeat event to let us know that it’s still healthy and connected. (Don’t confuse this with AJAX heartbeats of yore. This happens through a persistent EventSource connection, and is therefore much more efficient).
If our EventSource encounters a problem it sends us an error event, at which point we turn on offline mode throughout our app and attempt to reconnect. Alternatively, if our heartbeat times out, we enter offline mode.
Testing offline locally
It can be difficult to test offline mode while developing locally because you’re not actually using the Internet; turning off your wifi won’t trigger offline mode since you’re serving your API locally.
We tried several approaches to simulate server loss: IPtables, local tunnels, and plain old killing the local server. Our favorite ended up being local tunnels. The localtunnel NPM package lets you expose a local port to the wider Internet. For example, running
lt --port 8000 will spit out a URL you can visit from anywhere, and connect to your local 8000 process. Then, when you want to go offline, you can terminate the local tunnel and cause any browser connected through that tunnel to lose its connection to the local API server.
What can we do offline?
There are different approaches to enabling functionality offline. Offline.js aims to make all your features available offline by storing all AJAX requests a user makes while offline, then firing them off once the user regains their connection.
We didn’t take this approach because there is a lot of functionality in our app that just doesn’t make sense offline. Changing your email, for example, requires server validation for uniqueness. Searching against ElasticSearch or anything similar requires a connection, and so on.
In our app, when a user is offline we store the data in an IndexedDB. We define
.sync() methods for all our collections that intelligently merge this local DB with that on the remote server once the user goes back online. This works well because we can call
.sync() whenever the user regains connection; we don’t have to keep a detailed log of all the actions a user took offline and then try to replicate that once online. This cut down on the amount of state we have to manage and delegates “going online” to an easy
Clearing your application cache in different browsers
This last tip is regarding some rather frustrating housekeeping when dealing with offline apps: clearing the damn application cache. This is surprisingly difficult! Here’s how to do it in each browser:
- Chrome: Visit chrome://appcache-internals/ and click “remove” on the application cache for your domain, as well as localhost.
- Firefox: Firefox->Preferences->Advanced->Network tab->Offline Web Content and User Data->Clear now
- Safari: Safari->Preferences->Privacy->Remove All Website Data
- IE: LOL still trying to figure this one out
After clearing your cache some browsers require you to refresh twice: once to get the new app cache, and once more to use the newly downloaded assets.
Implementing an offline mode is a great UX upgrade for your users, since it makes your app robust and reliable even on shoddy networks. I hope these tips help ease the pain of developing offline features. Let me know your favorite offline mode tips in the comments!