Using Google Analytics within CocoonJS

CocoonJS is a great platform to use if you aim to release your HTML5 game on Google Play or the App Store. I have myself decided to use it to release my latest game on Google Play using CocoonJS.

CocoonJS offers great performance while claiming to require almost no modification. Unfortunately, that is not exactly true. I am pretty sure that if you want to export your game to CocoonJS, the first compilation will not be the final one.

One problem that I've run into lately was to be able to use Google Analytics in CocoonJS. Indeed, Google Analytics prevents you from using it on a local files, because it required HTTP or HTTPS protocol.

One way to fix that would be to host the analytics.js file locally and to edit it. I will not cover this method since I did not try it, and I don't like the idea of hosting that file by myself.

Instead, I used one of CocoonJS's cool features: the ability to load a webview layer at the same time as the Canvas+. You can read about this feature from Ludei on this article.

The idea is actually really simple when you know exactly what to do and how to do it.

What we're going to do is to host a very simple page on a web server that includes both Google Analytics and the CocoonJS API so we can communicate with it. Then we are going to ask this page to send data to Google Analytics. In other words, it is going to act as a proxy.

Here is the code for the page:

<!DOCTYPE html>
<html>
	<head>
		<script src="cocoon.js"></script>
		<script>
			// We want logging in the CocoonJS console
			CocoonJS.App.proxifyConsole();

			// Loading Google Analytics
			(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
			(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
			m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
			})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

			// Once we're loaded, we can inform the Canvas+ view that it can
			// start sending stuff
			console.log('GA interface ready, informing parent');
			CocoonJS.App.forward('window.gaInterfaceIsReady()');
		</script>
	</head>
	
	<body>
	</body>
</html>

As I said, the page only includes Google Analytics, and then informs the parent page - the game - that it is ready to forward calls.

Don't forget to include CocoonJS API in the cocoon.js file. Strangely, I haven't found a nice place to get the API, so I usually get it by downloading an example.

Once you have this ready, host it on a server so you can include it from the game.

Now that we have our proxy ready, we can start calling it from our game.

As I assume you will be releasing your game on the web too, the first trick is going to be to create our own ga() function that will not make direct calls to Google Analytics, but will call our proxy instead.

The second trick is that the proxy might take a while to load, therefore you cannot call it directly, otherwise you might lose some data. In order to avoid this problem, we are going to use a queue, and we are going to fill it up until the proxy is loaded. Then, once the proxy is ready, we can flush that queue and make direct calls.

The third and last trick is not to listen to the page loading, but to wait for a call from the page itself. The main reason for that is that I've noticed that the loading event is usually fired no matter if the page is loaded or not. If you check the proxy code, you can see that line:

CocoonJS.App.forward('window.gaInterfaceIsReady()');

Now that we have acknowledged these three points, we can start writing the code:

if(!navigator.CocoonJS){
	// Regular analytics code, no need to load it from anywhere
	(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
	(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
	m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
	})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
}else{
	var interfaceReady = false;
	var queue = [];

	var flushQueue = function(){
		var cmd;
		while(cmd = queue.shift()){
			forwardCmd(cmd);
		}
	};

	var forwardCmd = function(cmd){
		CocoonJS.App.forwardAsync(cmd);
	};

	var addToQueue = function(cmd){
		queue.push(cmd);
		if(interfaceReady){
			flushQueue();
		}
	};
	
	// CocoonJS loading event doesn't work too well, so we need to wait for an actual answer
	window.gaInterfaceIsReady = function(){
		interfaceReady = true;
		flushQueue();
	};
	
	console.log('Creating GAI interface');
	CocoonJS.App.loadInTheWebView("http://path.to/your/proxy.html");

	// Defining GAO to add stuff to the eventqueue 
	window.ga = function(){
		var args = '';
		for(var i = 0 ; i < arguments.length ; i++){
			if(i > 0){
				args += ',';
			}
			args += JSON.stringify(arguments[i]);
		}

		var cmd = 'window.ga(' + args + ')';
		addToQueue(cmd);
	};
}

// Make your calls to ga() here!

And that's pretty much it. The code should be self-explanatory once you've read all my explanations.

You may notice that I never show the webview. This is simply because its Javascript will still be run, and because I don't need the page content to be displayed.

You can use this method to communicate with other external services, or even to add a DOM layer to your game. Just keep in mind that you can only run one webview at a time, so you will need to include everything on the same page.

I might cover other CocoonJS tricks in the future, but that's it for now.

Hope that helps!

Polygon Battle + various updates >