Simple Slot machine game using HTML5 Part 4: Offline mode
May 5, 2013 3 Comments
This is the fourth part of the Slot machine game in HTML5 (previous parts 1, 2, and 3) and this time we modify the game to support HTML5 offline mode, also known as HTML5 Application Cache.
Try out offline supported version here.
Word of warning. HTML5 offline mode is powerful but very fragile feature. It’s tricky to get right, but once you get it to work the mobile user experience can be very native app like.
Some problems you will encounter
- Browser refresh logic is confusing. Especially the fact that browser does not use updated manifest and resources when they change but only after next reload. Fortunately Javascript workarounds exists.
- Application cache file maintenance needs diligence. For example, browser will not reload any assets if this file is not modified.
- Externally linked resources do not generally work offline. This makes CDN use difficult.
- Web server has to use right MIME type and cache settings to reliably use application cache files. Most web servers don’t do this in default configuration.
- No reliable way to detect if page was loaded in online or offline mode.
- Chrome bypasses some restrictions (e.g. cross-domain issues) in the specification and what works in Chrome may not work anywhere else.
- Each browser has slightly different meaning and heuristic for using offline mode. For example if browser can load some unrelated pages but can’t currently load your app page it may not show your page in offline mode and simply shows “Can not reach the server error”. This may happen especially if it knows from last load that manifest has been updated. Then at other times, it may load page few times in offline mode even when connectivity has returned.
Manifest file
Web page must use application cache manifest file to support offline mode. This manifest file is specified in html tag of the page.
<!DOCTYPE html> <html manifest="slots.appcache"> <head> ...
The file has listing of all content that page needs. For detailed explanation of each section, refer to Beginners guide to HTML5 application cache
CACHE MANIFEST # version 10 NETWORK: # here goes resources that must be never cached js/online.json CACHE: audio/nowin.mp3 audio/nowin.ogg audio/roll.mp3 audio/roll.ogg audio/slot.mp3 audio/slot.ogg audio/win.mp3 audio/win.ogg css/webfonts.css css/reset.css css/slot.css css/Slackey.ttf img/build-64.png img/cash-64.png img/energy-64.png img/gold-64.png img/goods-64.png img/loading.gif img/staff-64.png js/slot.js js/jquery.min.js index.html
Web Fonts
Web fonts must be hosted locally if you want to use them offline. Note that some web fonts may have licensing restrictions for local hosting.
<link type='text/css' rel='stylesheet' href="css/webfonts.css"/>
The webfont.css
defines the font face and loads true type file.
@font-face { font-family: 'Slackey'; font-style: normal; font-weight: 400; src: local('Slackey'), url(Slackey.ttf) format('truetype'); }
The file Slackey.ttf
is hosted locally in css
directory.
Web Server Support
Web server must use correct MIME type for text/cache-manifest
application cache manifest. For example, in NGINX web server edit the mime.types
and add following file type to MIME type mapping.
text/cache-manifest appcache;
Browsers should check manifest every time page is loaded online, but it may not do this often enough if cache control is too long. Therefore, set short cache lifetime for the manifest files by adding this inside server
section of NGINX configuration file. This forces cache lifetime of 1 minute to all *.appcache
files.
# set 1 minute cache life for HTML5 offline manifests location ~* \.(appcache)$ { expires 1m; }
Verify with cURL that server response Content-Type
has right MIME type and that the Expires
and/or Cache-Control
have correct 1 minute cache life time. If you get 404 error, make sure that site root configuration is set in http
section.
$ curl -I http://localhost:8081/slots.appcache HTTP/1.1 200 OK Server: nginx/1.4.0 Date: Sun, 05 May 2013 04:43:32 GMT Content-Type: text/cache-manifest Content-Length: 38 Last-Modified: Sun, 05 May 2013 04:25:28 GMT Connection: keep-alive ETag: "5185df38-26" Expires: Sun, 05 May 2013 04:44:32 GMT Cache-Control: max-age=60 Accept-Ranges: bytes
Detecting online status
Currently only “reliable” method to do is to make Ajax request and check the response. There are some caveats
- Request may fail for other reasons, and this does not mean browser is in offline mode
- Offline status may change while user is in page, you may want to do repeat polling check.
- User may be in public WiFi that redirects requests to login server. This can confuse your app that gets response but is not what was expected.
Slots game checks online status in parallel while game loads and only on startup. Slots game does not really need to know if it’s online or offline, but just writes the status on screen for debugging purposes.
<script type="text/javascript">$(function () { var game = SlotGame(); // Attempt loading static json file from server to detect online or offline mode. // The url has unique random parameter to avoid browser or proxy caches $.ajax({ url: 'js/online.json?ts=' + (~~new Date()), dataType: 'json', success: function(data) { if ( data.online ) { game.setOnlineStatus(true); } else { // might be online, but we didn't get expected response. Could be // e.g. Wifi login page. game.setOnlineStatus(false); } }, error: function() { game.setOnlineStatus(false); } }); });</script>
Otherwise loading images, audio and other content should be fully transparent to your app. Think twice before doing separate logic for online and offline as things will get difficult. Best advice I can give is to that you write code for online use with proper handling for Ajax errors. In this way when app loads in online mode but loses network later in session (e.g. when user goes in subway tunnel), the experience does not break completely.
Testing
This is the part where things get interesting, offline is tricky to test because of caching and browser reload logic. See detailed lamentation about subject here in Dive into HTML5.
These are the best practices I’ve come up with. First, set browser manually to offline mode to try things out. e.g. in Firefox this is enabled from File->Work Offline.
Second, if you develop the game from local server, do not use http://localhost
as host, but use real domain name that resolves to localhost. In this example I’ve used http://hexxie.com
that supports wildcard subdomain. Any subdomain resolves to address 127.0.0.1
.
$ nslookup anything-goes-here.hexxie.com Server: 192.168.1.254 Address: 192.168.1.254#53 Non-authoritative answer: Name: anything-goes-here.hexxie.com Address: 127.0.0.1
In this way you can always start from scratch the offline debugging simply by changing subdomain name. For example I just used slotsoff1.hexxie.com, slotsoff2.hexxie.com, … etc.:
Note that at least Firefox asks each time if you allow offline content.
Before each deploy, remember to increment the version comment in manifest file, so web server notices that the file has changed and browser will refresh it on next load. Server does not look inside the manifest file, so it does not matter how you change the file, as long as it’s changed.
Good Luck!
Code is available in Github.