Lessons learned from detecting Apache Cordova

If you have seen my Twitter feed over the last few weeks, you might have noticed that I’ve been using Apache Cordova lately. Since I’m in (another) holding pattern between tracking fixes, patches, and issues between Phaser and CocoonJS, I’ve been investigating other frameworks that might be better.

So far, while Cordova definitely has its own problems, the fact that it’s open source is a huge plus at the moment. It’s not as fast as CocoonJS is (depending on the drawing operations), but it has also been around for much longer and its documentation is pretty good. (Another mark, if I’m being honest, against CocoonJS. They’ve gotten better, for sure, but it’s still hard to find things.)

Unfortunately, detecting if a page is being rendered within the Cordova environment is something of a misadventure. Because it creates a WebView on the native platform, all the normal events get triggered, so testing for ‘load’ or ‘DOMContentLoaded’ don’t really help. Instead, you must wait for the ‘deviceready’ event.

On the surface, then, it would seem to be pretty straightforward for detecting Cordova. Just wait for the ‘deviceready’ event. Well, see, here’s the problem with that approach: it will never trigger in another environment. If you are testing on a desktop, for example, your code will never execute because the ‘deviceready’ event will never be fired and caught by your code.

So, the solution is to detect Cordova using some other method, add an event listener for the ‘deviceready’ event, and then load your project. Seems easy enough, right? I mean, you would think, anyway. However, detecting Cordova before detecting Cordova’s ‘deviceready’ event can get a little messy.

Detecting the FILE protocol

var isCordovaApp = (document.URL.indexOf('http://') === -1 && document.URL.indexOf('https://') === -1);
view raw filenot.js hosted with ❤ by GitHub
var isCordovaApp = (document.location.protocol === "file:");
view raw fileis.js hosted with ❤ by GitHub

Because Cordova loads everything via the FILE protocol, one way to detect it is to check the protocol being used. However, this solution ignores the possibilities that the page might have been opened on a desktop or via a mobile browser outside of the Cordova environment. A passable approach, but not ideal, clearly.

User agent testing

if (navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|IEMobile)/)) {
document.addEventListener("deviceready", onDeviceReady, false);
} else {
onDeviceReady(); //this is the browser
view raw useragent.js hosted with ❤ by GitHub

Like detecting the FILE protocol, this is a passable approach, but it also suffers from the same problem: if the page is opened via the FILE protocol on a browser outside of the Cordova environment, the ‘deviceready’ event will never trigger.

Checking the window object

var isCordovaApp = (typeof window.cordova !== "undefined");
view raw window.js hosted with ❤ by GitHub

Ultimately, this is what I decided to use. While it is the easiest to defeat via code, it is also surprising accurate for being so simple. As soon as the page begins to load, Cordova will create a window.cordova property. Even before the ‘deviceready’ event fires, this will exist for checking against. (And is highly unlikely to exist in another environment or be created by accident.)

An example using both window.cordova detection and listening for the ‘deviceready’ event

<!DOCTYPE html>
<title>Device Ready Example</title>
<script type="text/javascript" charset="utf-8" src="cordova.js"></script>
<script type="text/javascript" charset="utf-8">
var isCordovaApp = (typeof window.cordova !== "undefined");
if(isCordovaApp) {
document.addEventListener("deviceready", loadProject, false);
} else {
function loadProject() {
//Load project code here.