Migrating AngularJS html5mode links to Cordova

Gainstrack is implemented both for the desktop and for mobile – a true hybrid app. 90% of the code and views are shared with 10% having to be specifically customised.

One key difference is that a desktop AngularJS app looks much better with html5mode enabled so that URLs look natural without the # character. However, Cordova apps must have html5mode disabled. Thus links in vanilla <a href=”/foo”> tags would only work on one platform or the other but not both (only desktop in this case). It can be worked around by having links on both platforms like <a href=”#/foo”> and disabling html5mode link rewriting on the desktop side. However, clicking such a link on the desktop side would cause a page reload while Angular removes the #.

After hours of trying different solutions including Grunt building two different targets, trying to get html5mode working on Cordova, creating a urlFor method to put links in, in the end this simple snippet solved all problems on the mobile side.

angular.module('Gainstrack').directive('a', function() {
  return {
    priority: 1,
    restrict: 'E',
    link: function($scope, $element, $attrs) {
      $attrs.$observe('href', function (value) {
        if (value && value.indexOf('/') === 0) {
          $attrs.$set('href', '#' + value);
        }
      });
    }
  };
})

With this snippet only on the mobile side javascript, vanilla html5mode links are rewritten to hashbang links as required on the mobile side. Problem solved

Gainstrack 0.0.8 updates

A critical bug was found in 0.0.6 where one couldn’t edit historic workouts. That’s now fixed in 0.0.8. Assuming no more bugs found, I’ll also push this to the Android store.

In addition, thanks to feedback from “britPaul”, the workout planner is starting to make progress again – it is now unit aware like the rest of the site, able to handle planning in lbs or kgs. A fair amount of work is still needed for it to be truly useful though.

The only other technical update is the removal of the jQuery dependency. AngularJS doesn’t need jQuery and neither do I. That reduces the 3rd party javascript payload by 20%, which should result in slightly faster loads.

One final mention is my anecdotal testing of the mobile site is that it is definitely performing better on my phone! With all the changes, I can’t tell if it is Cordova, Angular or what but the speed increase is definitely useful.

Gainstrack 0.0.6 Updates

The website has been updated and the Android app will be updated in the app store shortly. Feature-wise, the main items are to fix the previously mentioned annoying issues

  • Navigating away from the app mid-workout and coming back – your workout will still be there
  • Using the app without internet reception will still allow you to record your workout
  • A news link so you can see this blog for updates

The last feature is only really for the Android app but if you are using the iPhone app, you can still pre-load by opening in reception and then using while out of internet reception.

Behind the scenes, a bunch of technical updates have been maded

Ideally these changes won’t have any functional changes but should boost performance a little by using libraries a year newer. On the other hand there may be a few glitches on the way. If so, please email or message me.

iPhone bug with window.alert in web apps

I thought I had a crazy bug. My iPhone tester reported that after closing a pop-up alert, the app would force close. This got me really worried since I don’t think I could cause a browser to crash even if I tried!

It turns out various flavours of iOS have a bug whereby alert/confirm in an event handler would cause the browser to shutdown. C’mon apple! It’s bugs like these that cause developers to have to create crazy workarounds to make things work across browser. The workaround in this case for AngularJS is to delay the alert

$timeout(function(){window.alert('My alert');});

CSRF protection with AngularJS and Mojolicious

Gainstrack is a single page application based on AngularJS that makes Javascript calls to a Mojolicious backend. In the modern age, one just protect against CSRF attacks that change user state triggered by malicious code in a pages on a 3rd party website.

A good first line of defence is to ensure that all requests that may change state are restricted to POST requests. This is as simple as ensuring your route accepts nothing but post

$r->post('/command/doStuff') # Not $r->any

This protects against many attacks caused from sites with XSS vulnerabilities allowing hackers to place carefully crafted IMG tags on such vulnerable sites. However, it doesn’t stop carefully crafted iframes+javascript from executing a POST attack as this Stackoverflow question demonstrates.

For further protection, we should generate a server side token that the client side passes back only via Javascript. This provides protection against CSRF because the Javascript that can process the token is protected by same origin policy. There are various intricacies in implementing CSRF token properly. Fortunately, both AngularJS and Mojolicious have in-built help for CSRF protection – it is just a matter of gluing things together!

Angular $http documentation describes how it has in-built support to use a XSRF-TOKEN cookie to subsequently add the token back via a X-XSRF-TOKEN header. So we just need to support this protocol on the Mojolicious side. Fortunately, Mojolicious already has token generation support. Thus all you need on one your app entry points is to have

$c->cookie('XSRF-TOKEN' => $c->csrf_token, {path => '/'});

Angular will handle this automatically. To perform CSRF validation, simply compare the header

my $csrf = $c->req->headers->header('X-XSRF-TOKEN') // '';
($csrf eq $c->csrf_token) or die("CSRF attack was foiled");

And with no further change on the client side, you have CSRF protection! Not bad for 3 lines of code.

Note that the above doesn’t seem to work of the site is deployed via Cordova/PhoneGap application. I had to do a workaround for that… details available upon request.

AngularJS html5mode with links back to server

Having enabled successfully enabled html5mode on my web application, I accidentally broke a browser link to download some JSON from the server, since Angular now interprets it as a route.

Thanks to http://blog.panjiesw.com/posts/2013/09/angularjs-normal-links-with-html5mode/ for posting an explaining a full answer.

Adding target=”_self” to the link fixes it but here are the options from the Angular docs

In cases like the following, links are not rewritten; instead, the browser will perform a full page reload to the original link.

  • Links that contain target element
    Example: <a href="/ext/link?a=b" target="_self">link</a>
  • Absolute links that go to a different domain
    Example: <a href="http://angularjs.org/">link</a>
  • Links starting with ‘/’ that lead to a different base path
    Example: <a href="/not-my-base/link">link</a>