This post details some of the issues I hit while trying to detect "true" offline status in HTML 5, specifically in Safari on the iPad (iOS 3.2.2), and how I worked around them. Hopefully it will help others who are trying to use HTML 5 in offline mode. It assumes you are familiar with the requirements for taking HTML offline.
To me one of the failings of the current HTML 5 spec as written is that it talks about how to take a Web site offline, but notes in the documentation about the navigator.onLine property and the ononline event that "This attribute is inherently unreliable. A computer can be connected to a network without having Internet access." So detecting you're offline can be a bit difficult.
And in fact, while researching I found that only later versions of IE (8?) truly detect when there's no network connectivity, and that other browsers may only return false from navigator.onLine if the user has chosen that browser's "work in offline mode" option, regardless of their actual network connectivity. And such is iPad's Safari, which simply lies - it blithely returns true whether you're online or offline (airplane mode or wireless manually turned off). So, if you're reading someone's answer to a post on "How do I detect if I am offline" and they say to check navigator.onLine and/or implement the ononline/onoffline events, they don't know what they're talking about.
If you are writing a cross-browser app that may try to "phone home" when it has network connectivity, but otherwise interacts with local storage when offline, you will have to roll your own connectivity detection. That's what we're here for. There are quite a few people who suggest making an AJAX callback to your Web site, but few that actually show it, so even though it's relatively simple, I am going to show you how.
Let's review some basic code. First, here is the manifest file, cache.manifest:
Basically, we want to download various files and cache them locally, but we do not want to cache the ping.js file (we'll see why later), so we put it under the NETWORK section.
Here is a bare index.html with code for downloading and swapping cache (and to help troubleshoot):
The only applicationCache event handler really needed is updateready, although the others come in handy when debugging, especially on a platform like the iPad. Make sure you turn on the console in iPad’s Safari - that's about the only on-device debugging you’re going to get (along with the web server logs - those can be helpful, too).
Before we go on, here are some notes on things I've learned while pursuing this:
- The files specified in the manifest will be downloaded asynchronously and will not (necessarily) come down in the order specified in the file.
- If you are offline the applicationCache error event handler will be called. It seems natural that you could use that for offline detection, except that it gets called for other reasons, too, e.g., a malformed manifest file, unavailable resources pointed to by the manifest file (401s, 404s, 5xx), etc. Thus it seemed cleaner to me to break out offline detection into its own test.
- Note the comment in the updateready handler. If you don't force a page reload as in the code shown, then you will have to refresh the page manually to get an updated offline cache to be used by the browser in the current session.
- Changing the manifest file to force a re-download and even clearing cache doesn't always really re-download everything, especially on the iPad. A few times I've had to power cycle the iPad to make sure cache is really cleared. This was especially true of the web page and its embedded script itself.
- Note my prior blog post on a gotcha specific to manifest files and iPad's Safari.
- Because your browser's cache also gets involved, it can be helpful during development and debugging to turn off caching directives in your Web server. You may want to do this anyway, otherwise it could be hard to push out changes, even by changing the manifest file, since it could be cached by the browser, too - see the debugging section of Dive Into HTML 5's Let's Take This Offline chapter for details. As that section's title says, "Kill me! Kill me now!"
With that out of the way, let's look into a way to determine whether we're really, really offline. There are quite a few resources on the Web about this, but you have to piece them together, so I thought I'd provide it here as simple example code. Note that this code requires jQuery.
Now, the first thing to notice about the code is that we hook the new(er) online and offline browser events, along with checking navigator.onLine's status. I only do this because there may be a browser, somewhere, some day, that actually implements all this correctly. Instead, once navigator has lied to us and told us we're online, we're going to double-check anyway by performing a simple AJAX call back to our site to retrieve a given (non-cached, non-offline) file. In our case this is ping.js:
Note this is crafted and retrieved as a JSON result, but it could really be anything because we don't use the contents, we simply look for success in pulling it down. The important thing is that it is not cached. Hence you have to make sure it is specified in the NETWORK section of the manifest and that your server sends HTTP headers down telling the browser not to cache the file, as well as telling the AJAX call not to cache it, either (see the cache parameter on the jQuery ajaxSetup call above).
The AJAX request has a timeout set to five seconds (5,000 milliseconds). You can adjust that up or down based on your typical connectivity speed. Right now the code treats any error as being "offline." That may not be literally true - perhaps the site is down. But in that case, if you can't "phone home," it really doesn't matter why. For all practical purposes you are "offline." Some could argue that there would still be the ability to perhaps access another site (using JSONP or a script tag), or send email or whatever, but for my purpose this is Good Enough.
Some final points:
- The checkNetworkStatus function is called as soon as the document is ready (using jQuery's ready function). It may very well fire before the manifest checking is finished, even if everything's already cached (and in fact, it does so pretty consistently for me). So when looking at console output don't expect the output from showNetworkStatus to be the last lines.
- When doing any debugging for HTML 5 offline capabilities, especially on mobile devices, the console is your friend. So debug like it's 1979!
Well, I hope this simple example helps and saves you some time. If it does or if you have recommendations on how to do it better, feel free to leave a comment!