Offline Testing In Chrome

Offline - No Wifi
Offline testing in Chrome – No more WiFi toggling

Despite ever expanding web of internet connectivity, no modern web app can expect not to work decently when offline.

Testing offline functionality, though, can be a bit of a bummer for people like me who develop almost exclusively in/for Google Chrome – unlike the old Internet Explorer, it doesn’t have a quick to access ‘Offline Mode’.

Not being a professional developer, I didn’t have extensive tools at my service to test my app in offline mode, and switching WiFi off-on was becoming tiresome. This is the solution I’ve ended up with, and if you’re looking for offline testing in Chrome, here’s an easy way:

1. Install the Proxy SwitchySharp extension from Chrome Web Store.

2. In extension’s settings, set up a proxy to a non-existent IP Address, preferably on your local network. You could even set it to localhost/127.0.0.1 if you aren’t running a server on local machine.

Proxy Switchy Sharp Settings
Proxy SwitchySharp Extension Settings

3. Now every time you need to switch to offline mode, all you have to do is go to the extension’s browser button, and select the local-profile you created.

 

Browser Button - Select Proxy
Browser Button – Select proxy to go offline.

4. When you want to go back on-line, just select ‘Direct Connection’ or, if you have a corporate proxy, then that.

That’s it! No more toggling WiFi to test offline mode for your web apps.

Online/Offline Daemon

Completed a big task on Friday, though not the way I wanted it – background, auto synchronisation of notes (including changes, deletions, etc) between server and browser app.

HTML5 has introduced the powerful features that provide in-built features for managing actions such as synchronisation when the browser detects an internet connection.

[sourcecode language=”javascript”]
if(navigator.online){
//do sync here
} else {
//store data offline
}

document.body.addEventListener("online", function () {
// do sync here
}, false);

document.body.addEventListener("online", function () {
// store data offline
}, false);

[/sourcecode]

Unfortunately, these features aren’t completely supported, or even uniformly implemented across browsers and, thus, are hardly usable in the current state.

All server requests in my app are implemented through xhr. So, I’m tapping into it and using this workaround:

  1. If an xhr call fails, an alternate function is called which:
  1. Sets a global variable to ‘offline
  2. Calls equivalent local functions which edit/create/delete/list from localStorage
  3. Starts a daemon using setInterval() that checks every sends a ‘Ping‘ xhr request to check if a server connection is available again
  • When the daemon can connect to server, it:
    1. Sets the global variable to ‘online
    2. Starts a function that syncs all local changes to server and then downloads a fresh list from server
    3. Kills the daemon using clearInterval() call

    There are quite a few problems with this approach. The ones I’m worrying about right now are:

    1. Memory, CPU and battery usage by that artificial daemon, specially on mobile devices.
    2. Unnecessary load on the server from handling those Ping requests, even though there’ll be only one successful one for every connect.

    Though, compared to the browser ‘online / offline’ properties, this approach has one advantage too – it can also handle instances when the internet connectivity is up but my server / app is down.

    One change I’ll probably be doing is fetching only the header instead of pinging the app and checking result – It’s faster and might be lighter on the server (need to check this bit). Got the idea for this from this blog post.

    [sourcecode language=”javascript”]
    x.open(
    // requesting the headers is faster, and just enough
    "HEAD",
    // append a random string to the current hostname,
    // to make sure we’re not hitting the cache
    "//" + window.location.hostname + "/?rand=" + Math.random(),
    // make a synchronous request
    false
    );
    [/sourcecode]

    Still, that doesn’t make up for a native browser capability to tell the app when the network connection has gone down. Waiting for it…

    Continue reading

    Yesss! Just got the localStorage and cached application to work offline for the first time!

    Now, to do that with real data, and extend beyond the listAll function.

    Offline App Development

    … is a pain.

    Mainly because once you add a file to the manifest, and thus to the browser’s app cache, you’re never sure if it’s getting updated. I spent most of last night and much of today just trying to get the browser to fetch the new version of html doc, and then the correct js file. Too bugged. It seems to be an issue primarily with Chrome & Chromium, but they are my primary development platform.

    Anyway, if despite all the changes to manifest, restarting servers, clearing browser cache, the browser is still fetching old cached files, here’s a way out:

    1. Open a new tab in chrome/chromium and go to ‘chrome://appcache-internals/
    2. Clear all cached app resources
    3. Refresh the page to confirm there are no more cached app resources
    4. Refresh your test page – it should fetch everything from the server now

    There’s another bit I learnt and implemented (though my implementation is pretty simplistic): Dynamic Cache Manifest.

    Basically, instead of writing the application manifest, for offline app storage, by hand and changing it every time you change a file, let your server-side script do the work. What I’ve done is :

    1. Hand-wrote a cache manifest file but with a comment line that includes a django tag {{ts}}.
    2. Changed my app.yaml so that instead of serving the file directly, the request for it is sent to my server script.
    3. In the script I use the handwritten cache manifest and replace the ts tag with a random number.

    What this ensures is that the browser is forced to fetch all elements in the app manifest every time because of the random number changing cache manifest on every fetch. This ensures that while I’m connected to the server and testing my js scripts, I get the latest versions every time. On the other hand, when I switch off the server and test the offline mode, the script still has resources available offline for rendering.

    There is a lot more you can do here with the dynamic cache manifest rather than just plug-in a random number. I learnt this trick from this blog at Cube, and there are a couple more handy tricks being used there, so I suggest reading that resource.

    Update: Seems like, as with script-initiated xhr requests, Chrome also makes multiple requests for the cache manifest from the server for every page load. When I use a randomizer function to send a ‘new’ cache manifest every time, this results in the two cache manifests being different to each other and thus Chrome doesn’t save any files. Not what I wanted, now? So, here’s a small change I made

    previous randomizer: int(random.random()*10000)
    new randomizer: time.strftime(‘%y%m%d%H%M’)

    Now, instead of returning a new resource every time, I return a new resource every minute. This means that the two simultaneous requests that Chrome fires, will get the same cache manifest. But a page refresh/load later, will return a new cache manifest. It’s working so far :)
    [Ofcourse, I know that there’s still a remote chance that the two requests will hit either side of the minute change. But the probability is small enough for me to take a chance.]

    Another code restructuring /

    Yesterday I restructured (I think they call it refactoring) all the code to integrate it into a single html template and a single python class. Also moved all calls to POST so nothing is visible, and editable, from the URL.

    Today, I realise I may have to restructure (refactor) the application again. Yesterday’s restructuring was to bring in simplicity and order. This time it is required for the ‘offline webapp’ bit. This is what happens when you use the learn as you go (cross the bridge when we get to it) approach.

    Looks like I’ve got another few long hours of boring, error-prone, code restructuring ahead of me :/