In some downtime, we decided it might be worth the time to dust off Gitr (the result of a little hackathon we had just before Christmas) and turn it into a proper, installable mobile app. Unfortunately, this post won’t be ending with a link to download it, BUT don’t let that stop you from reading as I learnt a lot about packaging web apps for mobile, testing websites for mobile and some of that information will be useful for future projects.
We might even end up revisiting Gitr as a packaged app someday, as we now have a nice starting point …
I decided to use Cordova (an open-source project) to package Gitr. Cordova is a framework that, in short, packages up some HTML and other static files and wraps them in a native application, while exposing a little bit of the platform’s API via JavaScript. It also works on more platforms than you can shake a stick at. Go on, I dare you, try to shake a stick at them. Since the Gitr UI was built entirely in JavaScript (with React), this meant it was a perfect candidate for the particular flavour of app that Cordova allows one to build.
Getting started was super simple. I started reading the Cordova documentation and quickly realised I’d need to have Xcode installed; the command line tools I’d gotten by with for so long wouldn’t cut it this time. This meant that I had at least a couple of hours to wait while Xcode downloaded. Four hours later (fibre will be sweet when it’s available at our office), I’d read most of the documentation, and my download had finished. Installing Cordova was a snap – we already use node for all of our build pipeline, so it was a simple matter of installing cordova via npm.
My first real activity (after getting set up) was to pull down the “hello world” example Cordova project. No problems there – it took a couple of minutes to pull down some dependencies but completed without issue. Once that was done, I had a nearly-blank slate to start with. All up, I downloaded the project
cordova create hello com.example.hello HelloWorld; cd hello
… configured it for my desired target …
cordova platform add ios
… built it and had it running in the iOS emulator in about 5 minutes. Hooray!
I cleaned out the example code and dropped a “production” build of Gitr (npm run build in the Gitr client repository) into the www area of the Cordova project. It’s apparently not quite that simple, because that didn’t work. Figures. One difference between a Cordova app and its browser counterpart is that there’s a little bit of initialization that has to happen before the application can really start doing things. This was easily remedied with a small change. At this point, Gitr was actually running in the iOS emulator!
It wasn’t quite perfect though …
For authentication, we use GitHub’s OAuth API. To communicate with this API, we open a new tab (or “window” in JavaScript parlance) and communicate with it via the postMessage API. This is all fine in a browser, as the code just pops open a tab where the user approves the OAuth grant, then that tab sends a message back saying “yep it all went okay.” In the Cordova world, there are no tabs, and there are no windows. It might already be obvious where this is going, but let me cut to the chase: I swapped the separate-window implementation for one that uses an iframe.
login() { var iframe = document.createElement("iframe"); // snip var self = this; var onMessage = function onMessage(ev) { if (ev.origin !== window.location.origin) { return; } if (typeof ev.data !== "object" || ev.data === null || typeof ev.data.token !== "string") { return; } window.removeEventListener('message', onMessage); document.body.removeChild(iframe); // snip }; window.addEventListener('message', onMessage); document.body.appendChild(iframe); iframe.src = baseUrl + '/auth?redirect_uri=' + escape('http://127.0.0.1:3000/login-callback.html');}
That nearly worked. I tried it in my browser via a development server (‘npm start’ in the Gitr-client repository) and it all operated as expected. So far I’d only had to touch two parts of the application, both in relatively minor ways. Life was good. I fired it up in the emulator and it didn’t work so well. Life was less good. I couldn’t figure out what was going on, because I had no idea what was breaking. Life was now looking pretty bad. Luckily James had done some Cordova stuff before, so he showed me how to use Safari’s developer tools with the iOS emulator (hint: ‘Developer’ -> ‘Simulator’ in Safari). That gave me a console.
Life was looking up.
My console told me that I was facing an origin-related restriction. Browsers (and by extension, Cordova) follow the “same-origin policy” to enforce some security constraints. Part of this policy mandates that a document (or script) on a non -‘file:’ url can’t interact with, affect, or redirect to, a document that is on a ‘file:’ url. A large part of our authentication process involves redirects and cross-window communication. It turns out that the files in a Cordova application aren’t served via an HTTP server, but rather read directly from the application using ‘file:’ urls. This means that our authentication process was simply not going to work, not without significant changes to either gitr or Cordova. Sigh.
Since I’ve already spent a couple of days on this project, we decided to put it aside for the moment and help out with some other, more pressing tasks. It definitely wasn’t a waste though. I’ve learnt a lot about Cordova, and some of the things that need to be considered while building something to work as a packaged mobile application. This knowledge will be used in other ways I’m sure and hopefully can help out anyone else with the same problems!
“Getting 90% of the way is easy but the last 10% is the hardest.”
The ninety-ninety rule sums the experience up quite well: “the first 90 percent of the code accounts for the first 90 percent of the development time. The remaining 10 percent of the code accounts for the other 90 percent of the development time”.