Using Google Analytics Measurement Protocol / Universal Analytics from Flash / AS3

I see that Google Analytics (as part of their new and recommended Universal Analytics solution) provides a simple RESTful interface called the Measurement Protocol for collecting analytics from a variety of platforms or applications.

How do you use this interface from a Flash/AS3 app? I'm creating a payload of url-formatted parameters according to the docs, but I'm getting a SecurityError because crossdomain.xml is not hosted at http://www.google-analytics.com/crossdomain.xml when making the URL request:

[SecurityErrorEvent type="securityError" bubbles=false cancelable=false eventPhase=2 text="Error #2048: Security sandbox violation: http://<mysite>/<myapp>.swf cannot load data from http://www.google-analytics.com/collect."]

Using either POST or GET, this call fails in the context of a web browser (though it succeeds in the context of AIR):

// i.e. var payload:String = 'v=1&t=event&ec=category&ea=action'+
//   '&el=label&tid=UA-xxxxxxxx-x&cid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'

var req:URLRequest = new URLRequest('http://www.google-analytics.com/collect');
req.method = URLRequestMethod.POST;
req.data = payload;
var urlLoader:URLLoader = new URLLoader();
urlLoader.load(req);

I need these analytics to work from either AIR or the Flash Player (on a web page).

Answers

or you can use a library
Google Universal Analytics for ActionScript 3.0

checkout as3-universal-analytics v0.8

https://github.com/zwetan/as3-universal-analytics/releases/tag/0.8

full support for: Flash Player, AIR, Redtamarin
it just works everywhere or almost everywhere :)

in your case

var config:Configuration = new Configuration();
    config.forcePOST = true;
var tracker:WebTracker = new WebTracker( "UA-12345-67", config );
    tracker.pageview( "/hello/world", "Hello World" );

Posted on by zwetan

As is noted in the URLRequest docs, cross-site scripting (xss) restrictions require a crossdomain.xml for POST requests. Since google doesn't host this file, you have to avoid POST. But the measurement protocol doc says it will accept either GET or POST. So you have to use a GET. GET with the above code still throws, but it turns out if you use a Loader instead of a URLLoader (as if you were going to access an image on the web, which is not covered by xss rules), it works:

// i.e. var payload:String = 'v=1&t=event&ec=category&ea=action'+
//   '&el=label&tid=UA-xxxxxxxx-x&cid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'

var req:URLRequest = new URLRequest('http://www.google-analytics.com/collect?'+payload);
var l:Loader = new Loader();
l.contentLoaderInfo.addEventListener(Event.COMPLETE, cleanup);
l.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, cleanup);
l.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, cleanup);
l.load(req);
function cleanup(e:Event):void {
  l.contentLoaderInfo.removeEventListener(Event.COMPLETE, cleanup);
  l.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, cleanup);
  l.contentLoaderInfo.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, cleanup);
}

You want the error listeners so that no errors pop-up, and you also need to clean them up to prevent memory leaks.

However, on mobile, I'd still use your original URLLoader code (as it has fewer allocations / events), perhaps using conditional compilation:

// i.e. var payload:String = 'v=1&t=event&ec=category&ea=action'+
//   '&el=label&tid=UA-xxxxxxxx-x&cid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'

ENV::AIR {
  var req:URLRequest = new URLRequest('http://www.google-analytics.com/collect');
  req.method = URLRequestMethod.POST;
  req.data = payload;
  var urlLoader:URLLoader = new URLLoader();
  urlLoader.load(req);
}

ENV::WEB {
  var req:URLRequest = new URLRequest('http://www.google-analytics.com/collect?'+payload);
  var l:Loader = new Loader();
  l.contentLoaderInfo.addEventListener(Event.COMPLETE, cleanup);
  l.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, cleanup);
  l.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, cleanup);
  l.load(req);
  function cleanup(e:Event):void {
    l.contentLoaderInfo.removeEventListener(Event.COMPLETE, cleanup);
    l.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, cleanup);
    l.contentLoaderInfo.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, cleanup);
  }
}


Posted on by Jeff Ward