diff --git a/src/C/unistd/which.as b/src/C/unistd/which.as new file mode 100644 index 0000000..fe01d9a --- /dev/null +++ b/src/C/unistd/which.as @@ -0,0 +1,102 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package C.unistd +{ + import C.stdlib.*; // getenv, exit, EXIT_SUCCESS, EXIT_FAILURE + import C.unistd.*; // access, X_OK, getlogin + import C.sys.stat.*; // stat, status, S_ISREG, S_IXUSR, S_IXGRP, S_IXOTH + + /** + * Locate a program file in the user's path. + * + *

+ * The which utility takes a command name and searches the path + * for each executable file that would be run had this + * command actually been invoked. + *

+ * + * @example Usage + * + * trace( "which bash = " + which( "bash" ) ); //output: which bash = /bin/bash + * trace( "which svn = " + which( "svn", true ) ); //output: which svn = /opt/local/bin/svn /usr/bin/svn + * + * + * @param name program name to look for. + * @param all if true list all instances of executables found. + * @return the full path of the program or the empty string if not found. + * + * @langversion 3.0 + * @playerversion AVM 0.4 + * @playerversion POSIX + + * + * @see http://docs.redtamarin.com/latest/C/stdlib/package.html#getenv() C.stdlib.getenv() + * @see http://pubs.opengroup.org/onlinepubs/9699919799/functions/getenv.html getenv() + * @see http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script Check if a program exists from a bash script + * @see http://www.opensource.apple.com/source/shell_cmds/shell_cmds-170/which/which.c shell command which + */ + public function which( name:String, all:Boolean = false ):String + { + var PATH:String = getenv( "PATH" ); + if( PATH == "" ) { return ""; } + + /* Note: + usual paths are + + /opt/local/bin + /opt/local/sbin + /usr/local/bin + /usr/bin + /bin + /usr/sbin + /sbin + */ + var paths:Array = PATH.split( ":" ); + paths.push( "." ); + + var is_there:Function = function( candidate:String ):Boolean + { + var fin:* = new status(); + /* Note: + getlogin() != "root" + should be + getuid() != 0 + */ + if( (access( candidate, X_OK ) == 0) && + (stat( candidate, fin ) == 0) && + S_ISREG( fin.st_mode ) && + ( (getlogin() != "root") || + ((fin.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0) ) ) + { + return true; + } + + return false; + } + + var found:String = ""; + + var i:uint; + var len:uint = paths.length; + var candidate:String; + for( i = 0; i < len; i++ ) + { + candidate = paths[i] + "/" + name; + if( is_there( candidate ) ) + { + if( all ) + { + found += (found == "" ? "": " ") + candidate; + } + else + { + return candidate; + } + } + } + + return found; + } + +} \ No newline at end of file diff --git a/src/Library.as b/src/Library.as new file mode 100644 index 0000000..47a9098 --- /dev/null +++ b/src/Library.as @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package +{ + import flash.display.Sprite; + + /** + * The basic framework Library to be included in the SWC. + * + *

+ * Note: This class is not a component, it is just + * a shim that allow to declare the SWC manifest and associate an icon file. + *

+ */ + [ExcludeClass] + [IconFile("uanalytics.png")] + public class Library extends Sprite + { + public function Library() + { + super(); + } + } +} \ No newline at end of file diff --git a/src/UniversalAnalytics.as b/src/UniversalAnalytics.as deleted file mode 100644 index a440835..0000000 --- a/src/UniversalAnalytics.as +++ /dev/null @@ -1,141 +0,0 @@ -package -{ - - import flash.display.Sprite; - import flash.events.Event; - import flash.events.MouseEvent; - import flash.system.Capabilities; - - import libraries.uanalytics.tracker.ApplicationInfo; - import libraries.uanalytics.tracker.DefaultTracker; - import libraries.uanalytics.tracker.HitType; - import libraries.uanalytics.tracker.SystemInfo; - import libraries.uanalytics.tracker.WebTracker; - import libraries.uanalytics.tracker.addons.DebugSharedObjectStorage; - import libraries.uanalytics.tracker.senders.DebugHitSender; - import libraries.uanalytics.tracker.senders.TraceHitSender; - import libraries.uanalytics.tracker.senders.URLLoaderHitSender; - import libraries.uanalytics.tracker.senders.URLStreamHitSender; - import libraries.uanalytics.tracking.Configuration; - import libraries.uanalytics.tracking.Tracker; - import libraries.uanalytics.utils.generateAIRAppInfo; - import libraries.uanalytics.utils.generateFlashSystemInfo; - import libraries.uanalytics.utils.getHostname; - - public class UniversalAnalytics extends Sprite - { - public function UniversalAnalytics() - { - super(); - - if( stage ) - { - onAddedToStage(); - } - else - { - addEventListener( Event.ADDED_TO_STAGE, onAddedToStage ); - } - } - - private function onAddedToStage( event:Event = null ):void - { - removeEventListener( Event.ADDED_TO_STAGE, onAddedToStage ); - main(); - } - - //public var tracker:DefaultTracker; - public var tracker:WebTracker; - - public function main():void - { - trace( "as3-universal-analytics" ); - trace( "Flash " + Capabilities.version + " (debug=" + Capabilities.isDebugger + ")" ); - - var hostname:String = getHostname(); - trace( "hostname = " + hostname ); - - var hitType:String = "pageview"; - //var hitType:String = "foobar"; - var valid:Boolean = HitType.isValid( hitType ); - trace( "valid = " + valid ); - - //var trackingID:String = "UA-94526-30"; //APP - var trackingID:String = "UA-94526-31"; //WEB - //var trackingID:String = "UA-00-00"; //ERR - - var config:Configuration = new Configuration(); - //config.forcePOST = true; - //config.forceSSL = true; - config.enableErrorChecking = false; - //config.anonymizeIp = true; - //config.overrideIpAddress = "127.0.0.1"; - //config.overrideUserAgent = "as3bot/1.0"; - //config.overrideUserAgent = "Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14"; - //config.overrideGeographicalId = "US"; - - config.senderType = "libraries.uanalytics.tracker.senders.TraceHitSender"; - //config.senderType = "libraries.uanalytics.tracker.senders.DebugHitSender"; - //config.senderType = "libraries.uanalytics.tracker.senders.URLLoaderHitSender"; - //config.senderType = "libraries.uanalytics.tracker.senders.URLStreamSender"; - - - var sender:TraceHitSender; - var sender2:DebugHitSender; - var sender3:URLLoaderHitSender; - var sender4:URLStreamHitSender; - - //tracker = new DefaultTracker( trackingID, config ); - //var tracker:DefaultTracker = new DefaultTracker( trackingID, config ); - //var tracker:DefaultTracker = new DefaultTracker( trackingID ); - //var tracker:WebTracker = new WebTracker( trackingID, config ); - tracker = new WebTracker( trackingID, config ); - trace( "trackingId = " + tracker.trackingId ); - trace( " clientId = " + tracker.clientId ); - - var sysinfo:SystemInfo = generateFlashSystemInfo( this ); - - //tracker.add( sysinfo.toDictionary() ); - - var appinfo:ApplicationInfo = new ApplicationInfo(); - appinfo.name = "My App"; - appinfo.ID = "com.something.myapp"; - appinfo.version = "1.0.0"; - appinfo.installerID = "Local testing"; - - //tracker.add( appinfo.toDictionary() ); - - //var air_appinfo:ApplicationInfo = generateAIRAppInfo(); - - //tracker.setOneTime( Tracker.NON_INTERACTION, "1" ); - var sent:Boolean = tracker.pageview( "/my page € web1", "NI - My Page Title with € web1" ); - //tracker.set( Tracker.APP_NAME, "My App" ); - //var sent:Boolean = tracker.screenview( "My Screen €2" ); - - trace( "sent = " + sent ); - - //DebugSharedObjectStorage( false, true ); - //DebugSharedObjectStorage(); - - var test:Sprite = new Sprite(); - test.graphics.beginFill( 0xffcc00 ); - test.graphics.drawRect( 0, 0, stage.stageWidth, stage.stageHeight ); - test.graphics.endFill(); - test.width = stage.stageWidth; - test.height = stage.stageHeight; - - stage.addChild( test ); - - test.addEventListener( MouseEvent.CLICK, onClick ); - } - - private function onClick( event:MouseEvent ):void - { - trace( "onClick()" ); - var sent:Boolean = tracker.pageview( "/my page € web1", "My Page Title with € web1" ); - trace( "sent = " + sent ); - } - - - } -} \ No newline at end of file diff --git a/src/crypto/generateRandomBytes.as b/src/crypto/generateRandomBytes.as new file mode 100644 index 0000000..55493de --- /dev/null +++ b/src/crypto/generateRandomBytes.as @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package crypto +{ + import flash.utils.ByteArray; + + import shell.Program; + + /** + * Generates a sequence of random bytes. + * + *

+ * Use generateRandomBytes() to generate cryptographic keys, + * strong identifiers, session ids, and so on. + * The random sequence is generated using cryptographically strong functions + * provided by the operating system. + * If the appropriate function is not available on an individual client + * computer or device, then an error is thrown. + *

+ * + *

+ * Note: this is a temporary solution as the current redtamarin runtimes + * does not implment flash.crypto.generateRandomBytes yet.
+ * It will work under Linux and Mac OS X but not under Windows (unless under a cygwin shell). + *

+ * + * @param numberRandomBytes the number of random bytes to generate, between 1 and 1024. + * @return a ByteArray containing the generated bytes. + * + * @playerversion AVM 0.4 + * @playerversion POSIX + + * @langversion 3.0 + * + * @see http://en.wikipedia.org/wiki//dev/random /dev/random + * @see http://stackoverflow.com/questions/3690273/did-i-understand-dev-urandom Did I understand /dev/urandom? + */ + public function generateRandomBytes( numberRandomBytes:uint ):ByteArray + { + if( numberRandomBytes < 1 ) + { + numberRandomBytes = 1; + } + + if( numberRandomBytes > 1024 ) + { + numberRandomBytes = 1024; + } + + var bytes:ByteArray = new ByteArray(); + + var get2bytes:Function = function():uint + { + /* Note: + This will only work under Linux and Mac OS X and only if + "/dev/urandom" is available. + We should test its presence and thorw an error if not found. + */ + var str:String = Program.open( "LC_CTYPE=C tr -dc 'A-F0-9' < /dev/urandom | head -c 2" ); + var b:uint = parseInt( "0x" + str ); + return b; + } + + var i:uint; + for( i = 0; i < numberRandomBytes; i++ ) + { + bytes.writeByte( get2bytes() ); + } + + bytes.position = 0; + return bytes; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/SimplestTracker.as b/src/libraries/uanalytics/SimplestTracker.as new file mode 100644 index 0000000..bc3e6ad --- /dev/null +++ b/src/libraries/uanalytics/SimplestTracker.as @@ -0,0 +1,262 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics +{ + + import flash.crypto.generateRandomBytes; + import flash.display.Loader; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.HTTPStatusEvent; + import flash.events.IOErrorEvent; + import flash.events.NetStatusEvent; + import flash.events.UncaughtErrorEvent; + import flash.net.SharedObject; + import flash.net.SharedObjectFlushStatus; + import flash.net.URLRequest; + import flash.net.URLRequestMethod; + import flash.utils.ByteArray; + + /* Note: + this code is not supported, not exception. + */ + [ExcludeClass] + public class SimplestTracker + { + private var _so:SharedObject; + private var _loader:Loader; + + public var trackingID:String; + public var clientID:String; + + public function SimplestTracker( trackingID:String ) + { + trace( "SimplestTracker starts" ); + this.trackingID = trackingID; + trace( "trackingID = " + trackingID ); + + trace( "obtain the Client ID" ); + this.clientID = _getClientID(); + trace( "clientID = " + clientID ); + } + + private function onFlushStatus( event:NetStatusEvent ):void + { + _so.removeEventListener( NetStatusEvent.NET_STATUS, onFlushStatus); + trace( "User closed permission dialog..." ); + + switch( event.info.code ) + { + case "SharedObject.Flush.Success": + trace( "User granted permission, value saved" ); + break; + + case "SharedObject.Flush.Failed": + trace( "User denied permission, value not saved" ); + break; + } + } + + private function onLoaderUncaughtError( event:UncaughtErrorEvent ):void + { + trace( "onLoaderUncaughtError()" ); + + if( event.error is Error ) + { + var error:Error = event.error as Error; + trace( "Error: " + error ); + } + else if( event.error is ErrorEvent ) + { + var errorEvent:ErrorEvent = event.error as ErrorEvent; + trace( "ErrorEvent: " + errorEvent ); + } + else + { + trace( "a non-Error, non-ErrorEvent type was thrown and uncaught" ); + } + + _removeLoaderEventsHook(); + } + + private function onLoaderHTTPStatus( event:HTTPStatusEvent ):void + { + trace( "onLoaderHTTPStatus()" ); + trace( "status: " + event.status ); + + if( event.status == 200 ) + { + trace( "the request was accepted" ); + } + else + { + trace( "the request was not accepted" ); + } + } + + private function onLoaderIOError( event:IOErrorEvent ):void + { + trace( "onLoaderIOError()" ); + _removeLoaderEventsHook(); + } + + private function onLoaderComplete( event:Event ):void + { + trace( "onLoaderComplete()" ); + + trace( "done" ); + _removeLoaderEventsHook(); + } + + private function _removeLoaderEventsHook():void + { + _loader.uncaughtErrorEvents.removeEventListener( UncaughtErrorEvent.UNCAUGHT_ERROR, onLoaderUncaughtError ); + _loader.contentLoaderInfo.removeEventListener( HTTPStatusEvent.HTTP_STATUS, onLoaderHTTPStatus ); + _loader.contentLoaderInfo.removeEventListener( Event.COMPLETE, onLoaderComplete ); + _loader.contentLoaderInfo.removeEventListener( IOErrorEvent.IO_ERROR, onLoaderIOError ); + } + + private function _generateUUID():String + { + var randomBytes:ByteArray = generateRandomBytes( 16 ); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + + var toHex:Function = function( n:uint ):String + { + var h:String = n.toString( 16 ); + h = (h.length > 1 ) ? h: "0"+h; + return h; + } + + var str:String = ""; + var i:uint; + var l:uint = randomBytes.length; + randomBytes.position = 0; + var byte:uint; + + for( i=0; i + * The equivalent of iOS / Android Google Analytics SDK + * for desktop and/or mobile AIR applications. + *

+ *
+     * Android SDK
+     * For each tracker instance on a device, each app instance starts with
+     * 60 hits that are replenished at a rate of 1 hit every 2 seconds.
+     * Applies to all hits except for ecommerce (item or transaction).
+     * 
+     * iOS SDK
+     * Each property starts with 60 hits that are replenished at a rate of 1 hit
+     * every 2 seconds.
+     * Applies to all hits except for ecommerce (item or transaction).
+     * 
+ * + *

+ * Features: + *

+ * + * + * @example Usage + * + * import com.google.analytics.tracker.AppTracker; + * + * var tracker:AppTracker = new AppTracker( "UA-12345-57" ); + * + * tracker.set( Tracker.APP_NAME, "My App" ); + * + * tracer.screenview( "Hello World" ); + * + * + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + public class AppTracker extends DefaultTracker + { + protected var _storage:SharedObject; + + /** + * Create a new AppTracker with no data model fields set. + * + * @param trackingId the Tracking ID + * @param config the tracker configuration + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + public function AppTracker( trackingId:String = "", + config:Configuration = null ) + { + super( trackingId, config ); + } + + /** + * @private + * + * Here the slight differences with the DefaultTracker + * + */ + protected override function _ctor( trackingId:String = "" ):void + { + _model = new HitModel(); + _temporary = new HitModel(); + + if( _config.senderType != "" ) + { + var S:Class = ApplicationDomain.currentDomain.getDefinition( _config.senderType ) as Class; + _sender = new S( this ); + } + else + { + _sender = new LoaderHitSender( this ); + } + + /* Note: + the tracker starts with 60 hits + that are replenished at a rate of 1 hit every 2 seconds. + */ + _limiter = new RateLimiter( 20, 2, 1 ); + + if( trackingId != "" ) + { + set( TRACKING_ID, trackingId ); + } + + var cid:String = _getClientID(); + if( cid != "" ) + { + set( CLIENT_ID, cid ); + } + + set( Tracker.DATA_SOURCE, DataSource.APP ); + } + + /** + * @private + * + * A storage mecanism based on SharedObject + * to save and/or restore the ClientId. + */ + protected override function _getClientID():String + { + // Load the SharedObject '_ga' + _storage = SharedObject.getLocal( _config.storageName ); + var cid:String; + + if( !_storage.data.clientid ) + { + // CID not found, generate Client ID + cid = generateUUID(); + + // Save CID into SharedObject + _storage.data.clientid = cid; + + var flushStatus:String = null; + try + { + flushStatus = _storage.flush( 1024 ); //1KB + } + catch( e:Error ) + { + //trace( "Could not write SharedObject to disk: " + e.message ); + } + + if( flushStatus != null ) + { + switch( flushStatus ) + { + case SharedObjectFlushStatus.PENDING: + // Requesting permission to save object... + _storage.addEventListener( NetStatusEvent.NET_STATUS, onFlushStatus ); + break; + + case SharedObjectFlushStatus.FLUSHED: + // Value flushed to disk" + break; + } + } + + } + else + { + // CID found, restore from SharedObject + cid = _storage.data.clientid; + } + + return cid; + } + + protected function onFlushStatus( event:NetStatusEvent ):void + { + // User closed permission dialog... + _storage.removeEventListener( NetStatusEvent.NET_STATUS, onFlushStatus); + + switch( event.info.code ) + { + case "SharedObject.Flush.Success": + // User granted permission, value saved + break; + + case "SharedObject.Flush.Failed": + // User denied permission, value not saved + break; + } + } + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/ApplicationInfo.as b/src/libraries/uanalytics/tracker/ApplicationInfo.as new file mode 100644 index 0000000..724686a --- /dev/null +++ b/src/libraries/uanalytics/tracker/ApplicationInfo.as @@ -0,0 +1,150 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker +{ + import flash.utils.Dictionary; + + import libraries.uanalytics.tracking.Tracker; + + /** + * The ApplicationInfo defines parameters meant to be used + * for the App / Screen Tracking. + * + *

+ * By default, all the parameters are empty (not initialised). + * Only the non-empty (or initialised) parameters wil be exported + * by the toDictionary() function. + *

+ * + * @example Usage + * + * // define the app properties manually + * var appinfo:ApplicationInfo = new ApplicationInfo(); + * appinfo.name = "My App"; + * appinfo.ID = "com.something.myapp"; + * appinfo.version = "1.0.0"; + * appinfo.installerID = "Local testing"; + * + * // pass it to the tracker + * tracker.add( appinfo.toDictionary() ); + * + * + * + * // automatically generate the app properties + * var appinfo:ApplicationInfo = generateAIRAppInfo(); + * + * // pass it to the tracker + * tracker.add( appinfo.toDictionary() ); + * + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.utils#generateAIRAppInfo() generateAIRAppInfo() + */ + public class ApplicationInfo + { + + /** + * The application name. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#APP_NAME Tracker.APP_NAME + */ + public var name:String = ""; + + /** + * The application identifier. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#APP_ID Tracker.APP_ID + */ + public var ID:String = ""; + + /** + * The application version. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#APP_VERSION Tracker.APP_VERSION + */ + public var version:String = ""; + + /** + * The application installer identifier. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#APP_INSTALLER_ID Tracker.APP_INSTALLER_ID + */ + public var installerID:String = ""; + + /** + * Creates an empty ApplicationInfo. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function ApplicationInfo() + { + super(); + } + + /** + * Export the initialised (non-empty) parameters. + * + * @return a Dictionary containing field/value pairs. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function toDictionary():Dictionary + { + var values:Dictionary = new Dictionary(); + + if( name.length > 0 ) + { + values[ Tracker.APP_NAME ] = name; + } + + if( ID.length > 0 ) + { + values[ Tracker.APP_ID ] = ID; + } + + if( version.length > 0 ) + { + values[ Tracker.APP_VERSION ] = version; + } + + if( installerID.length > 0 ) + { + values[ Tracker.APP_INSTALLER_ID ] = installerID; + } + + return values; + } + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/CliTracker.as b/src/libraries/uanalytics/tracker/CliTracker.as new file mode 100644 index 0000000..d6e4c74 --- /dev/null +++ b/src/libraries/uanalytics/tracker/CliTracker.as @@ -0,0 +1,215 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker +{ + import libraries.uanalytics.tracking.Configuration; + import libraries.uanalytics.utils.generateCLIUUID; + + import shell.FileSystem; + + import C.errno.*; + import C.sys.stat.*; + import C.unistd.*; + + /** + * A tracker specialised for Redtamarin applications. + * + *

+ * Features: + *

+ * + * + * @example Usage + * + * import com.google.analytics.tracker.CommandLineTracker; + * + * var tracker:CliTracker = new CliTracker( "UA-12345-67" ); + * + * tracker.pageview( "/hello/world", "Hello World" ); + * + * + * + * @playerversion AVM 0.4 + * @playerversion POSIX + + * @langversion 3.0 + */ + public class CliTracker extends CommandLineTracker + { + + /** + * Create a new CliTracker with no data model fields set. + * + * @param trackingId the Tracking ID + * @param config the tracker configuration + * @param verbose enable sender verbose mode + * @param debug enable sender debug mode + * @param senderErrorChecking enable sender error checking + * + * @playerversion AVM 0.4 + * @playerversion POSIX + + * @langversion 3.0 + */ + public function CliTracker( trackingId:String = "", + config:Configuration = null, + verbose:Boolean = false, + debug:Boolean = false, + senderErrorChecking:Boolean = false ) + { + super( trackingId, config, verbose, debug, senderErrorChecking ); + } + + /** + * @private + * + * A storage mecanism based on the File System + * to save and/or restore the ClientId. + * + * @see http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html Environment Variables + */ + protected override function _getClientID():String + { + + /* Note: + homeDirectory do special checks to really find the home directory + eg. if getenv( "HOME" ) is empty + it will then fetch the user login and check in different folders + like /var/users/login, etc. + + see https://github.com/Corsaair/redtamarin/blob/master/src/as3/shell/FileSystem.as + */ + var homedir:String = FileSystem.homeDirectory; + + if( homedir == "" ) + { + homedir = "/"; // root dir + if( _verbose ) + { + trace( "WARNING: use ROOT directory as HOME directory \"" + homedir + "\"" ); + } + } + + var exists:int = access( homedir, F_OK ); + if( exists < 0 ) + { + var created:int = mkdir( homedir ); + if( created < 0 ) + { + if( _verbose ) + { + trace( "WARNING: Could not create HOME directory \"" + homedir + "\"" ); + } + + if( config.enableErrorChecking ) + { + throw new CError( errno ); + } + + return super._getClientID(); + } + } + + var canread:int = access( homedir, R_OK ); + if( canread < 0 ) + { + if( _verbose ) + { + trace( "WARNING: Can not read from HOME directory \"" + homedir + "\"" ); + } + + if( config.enableErrorChecking ) + { + throw new CError( errno ); + } + } + + var canwrite:int = access( homedir, W_OK ); + if( canwrite < 0 ) + { + if( _verbose ) + { + trace( "WARNING: Can not write to HOME directory \"" + homedir + "\"" ); + } + + if( config.enableErrorChecking ) + { + throw new CError( errno ); + } + } + + // by that point we are sure the HOME directory exists and is readable/writable + // for ex: /home/username + + var savepath:String = homedir + "/.uanalytics"; + var s_exists:int = access( savepath, F_OK ); + if( s_exists < 0 ) + { + var s_created:int = mkdir( savepath ); + if( created < 0 ) + { + if( _verbose ) + { + trace( "WARNING: Could not create save directory \"" + savepath + "\"" ); + } + + if( config.enableErrorChecking ) + { + throw new CError( errno ); + } + + return super._getClientID(); + } + } + + // by that point we are sure our preference dir .uanalytics exists + // for ex: /home/username/.uanalytics + + var filepath:String = savepath + "/" + _config.storageName; + var cid:String = ""; + + var f_exists:int = access( filepath, F_OK ); + if( f_exists < 0 ) + { + // the save file does not exists yet + cid = generateCLIUUID(); + FileSystem.write( filepath, cid ); //we save the UUID + return cid; + } + else + { + // the save file already exists + var data:String = FileSystem.read( filepath ); + if( data != "" ) + { + cid = data; + } + else + { + var newid:String = generateCLIUUID(); + FileSystem.write( filepath, newid ); //we save + cid = newid; + } + } + + // somehow something went wrong and no clientId were found + if( cid == "" ) + { + cid = super._getClientID(); + } + + return cid; + } + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/CommandLineTracker.as b/src/libraries/uanalytics/tracker/CommandLineTracker.as new file mode 100644 index 0000000..69c4b65 --- /dev/null +++ b/src/libraries/uanalytics/tracker/CommandLineTracker.as @@ -0,0 +1,769 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker +{ + import flash.utils.Dictionary; + + import libraries.uanalytics.tracker.senders.BSDSocketHitSender; + import libraries.uanalytics.tracking.Configuration; + import libraries.uanalytics.tracking.HitModel; + import libraries.uanalytics.tracking.HitSampler; + import libraries.uanalytics.tracking.RateLimitError; + import libraries.uanalytics.tracking.RateLimiter; + import libraries.uanalytics.tracking.Tracker; + import libraries.uanalytics.utils.generateCLIUUID; + import libraries.uanalytics.utils.getCLIHostname; + + import shell.Domain; + + /** + * Default Google Analytics tracker implementation for the command-line. + * + *

+ * This tracker is to be considered as a basic common denominator, + * but you would want to customise it for different use cases. + *

+ * + *

+ * Important things to know: + *

+ * + * + * @example Usage + * + * import com.google.analytics.tracker.CommandLineTracker; + * import com.google.analytics.tracking.Configuration; + * + * // we want to override the default config + * var config:Configuration = new Configuration(); + * //we want to throw errors + * config.enableErrorChecking = true; + * + * /* be careful here + * the command-line have access to much less sender + * - the default BSDSocketHitSender + * will work everywhere but does not support SSL + * and so will nto work with "/debug/collect" which needs HTTPS + * - the CurlHitSender + * will work as long as curl is available on the system + * and because it reuse curl it supports SSL and "/debug/collect" + * - the TraceHitSender + * which does not use any mecanism to send to a URL + * but only trace(0 the data to send + * */ + * //we want to use the cURL sender so we can use debug + * config.senderType = "libraries.uanalytics.tracker.senders.CurlHitSender"; + * + * //replace this with a valid Tracking ID + * var trackingID:String = "UA-12345-67"; + * + * // if you want to see results on the standard output + * var verbose:Boolean = true; + * + * // if you want to use the validation servers + * var debug:Boolean = true; + * + * // if you want to see specific sender error for debugging + * var senderErrorChecking:Boolean = false; + * + * /* At the contrary of the trackers for Flash / AIR + * a command-line tracker need a bit more configuration. + * on top of the usual trackingID and config + * you will want to configure the verbose, debug and senderErrorChecking + * */ + * var tracker:CommandLineTracker = new CommandLineTracker( trackingID, + * config, + * verbose, + * debug + * senderErrorChecking ); + * + * + * @playerversion AVM 0.4 + * @playerversion POSIX + + * @langversion 3.0 + */ + public class CommandLineTracker extends Tracker + { + + protected var _verbose:Boolean; + protected var _debug:Boolean; + protected var _senderErrorChecking:Boolean; + + /** + * Create a new Tracker with no data model fields set. + * + * @param trackingId the Tracking ID + * @param config the tracker configuration + * @param verbose enable sender verbose mode + * @param debug enable sender debug mode + * @param senderErrorChecking enable sender error checking + */ + public function CommandLineTracker( trackingId:String = "", + config:Configuration = null, + verbose:Boolean = false, + debug:Boolean = false, + senderErrorChecking:Boolean = false ) + { + super(); + + if( !config ) + { + config = new Configuration(); + } + + _config = config; + _verbose = verbose; + _debug = debug; + _senderErrorChecking = senderErrorChecking; + _ctor( trackingId ); + } + + /** + * @private + * + * We want to move the constructor logic into a method + * so when we inherit it we can prevent the build and replace it + * with other custom builds. + */ + protected function _ctor( trackingId:String = "" ):void + { + _model = new HitModel(); + _temporary = new HitModel(); + + /* Note: + our default safe sender for the CLI is BSDSocketSender + but we want to be able ot dynamically instantiate + other type of sender with config.senderType + */ + if( _config.senderType != "" ) + { + var S:Class = Domain.currentDomain.getClass( _config.senderType ); + _sender = new S( this ); + + if( _config.senderType.indexOf( "CurlHitSender" ) > -1 ) + { + if( _verbose ) + { + S(_sender).verbose = _verbose; + } + + if( _debug ) + { + S(_sender).debug = _debug; + } + + if( _senderErrorChecking ) + { + S(_sender).enableErrorChecking = _senderErrorChecking; + } + } + + } + else + { + _sender = new BSDSocketHitSender( this ); + + if( _debug ) + { + BSDSocketHitSender(_sender).verbose = _verbose; + } + + if( _debug ) + { + BSDSocketHitSender(_sender).debug = _debug; + } + + if( _senderErrorChecking ) + { + BSDSocketHitSender(_sender).enableErrorChecking = _senderErrorChecking; + } + + } + + _limiter = new RateLimiter( 100, 10 ); + + if( trackingId != "" ) + { + set( TRACKING_ID, trackingId ); + } + + var cid:String = _getClientID(); + if( cid != "" ) + { + set( CLIENT_ID, cid ); + } + + set( Tracker.DATA_SOURCE, DataSource.COMMAND_LINE ); + } + + /** + * @private + * + * Here we obtain the client id + * in the default implementation we keep it simple + * and each time we will use this tracker we will obtain + * a new clientId. + * + * Ideally we would want to be able to save and reuse + * a clientId, see more advanced implementation. + */ + protected function _getClientID():String + { + return generateCLIUUID(); + } + + /** + * @private + * + * A very basic cache buster. + */ + protected function _getCacheBuster():String + { + var d:Date = new Date(); + var rnd:Number = Math.random(); + return String( d.valueOf() + rnd ); + } + + /** @inheritDoc */ + public override function send( hitType:String = null, tempValues:Dictionary = null ):Boolean + { + if( (trackingId == "") || (trackingId == null) ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "tracking id is missing." ); + } + else + { + return false; + } + } + + if( (clientId == "") || (clientId == null) ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "client id is missing." ); + } + else + { + return false; + } + } + + var copy:HitModel = _model.clone(); + copy.add( _temporary ); + + _temporary.clear(); + + if( tempValues != null ) + { + for( var entry:String in tempValues ) + { + copy.set( entry, tempValues[entry] ); + } + } + + if( ((hitType != "") || (hitType != null)) && + HitType.isValid( hitType ) ) + { + copy.set( HIT_TYPE, hitType ); + } + else + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "hit type \"" + hitType + "\" is not valid." ); + } + else + { + return false; + } + } + + + if( _config && _config.enableSampling ) + { + if( HitSampler.isSampled( copy, String(_config.sampleRate) ) ) + { + return false; + } + } + + if( _config && _config.enableThrottling ) + { + if( !_limiter.consumeToken() ) + { + if( _config && _config.enableErrorChecking ) + { + throw new RateLimitError(); + } + else + { + return false; + } + } + } + + if( _config && _config.enableCacheBusting ) + { + copy.set( CACHE_BUSTER, _getCacheBuster() ); + } + + //configuration options + + if( _config && _config.anonymizeIp ) + { + copy.set( ANON_IP, "1" ); + } + + if( _config && (_config.overrideIpAddress != "") ) + { + copy.set( IP_OVERRIDE, _config.overrideIpAddress ); + } + + if( _config && (_config.overrideUserAgent != "") ) + { + copy.set( USER_AGENT_OVERRIDE, _config.overrideUserAgent ); + } + + if( _config && (_config.overrideGeographicalId != "") ) + { + copy.set( GEOGRAPHICAL_OVERRIDE, _config.overrideGeographicalId ); + } + + + var err:Error = null; + try + { + _sender.send( copy ); + } + catch( e:Error ) + { + err = e; + } + + if( _config && _config.enableErrorChecking && err ) + { + throw err; + } + + if( err ) + { + return false; + } + + return true; + } + + /** @inheritDoc */ + public override function pageview( path:String, title:String = "" ):Boolean + { + if( (path == null) || (path == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "path is empty" ); + } + else + { + return false; + } + + } + + var values:Dictionary = new Dictionary(); + values[ Tracker.DOCUMENT_PATH ] = path; + + if( title && (title.length > 0) ) + { + if( title.length > 1500 ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "Title is bigger than 1500 bytes." ); + } + else + { + return false; + } + } + + values[ Tracker.DOCUMENT_TITLE ] = title; + } + + var hostname:String = get( Tracker.DOCUMENT_HOSTNAME ); + if( hostname == null ) + { + hostname = getCLIHostname(); + + if( hostname == "" ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "hostname is not defined." ); + } + else + { + return false; + } + } + else + { + values[ Tracker.DOCUMENT_HOSTNAME ] = hostname; + } + } + + return send( HitType.PAGEVIEW, values ); + } + + /** @inheritDoc */ + public override function screenview( name:String, appinfo:Dictionary = null ):Boolean + { + if( (name == null) || (name == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "name is empty" ); + } + else + { + return false; + } + } + + var values:Dictionary = new Dictionary(); + if( appinfo != null ) + { + for( var entry:String in appinfo ) + { + values[ entry ] = appinfo[ entry ]; + } + } + + var app_name:String = get( Tracker.APP_NAME ); + if( app_name == null ) + { + if( !(Tracker.APP_NAME in values) ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "Application name is not defined." ); + } + else + { + return false; + } + } + } + + values[ Tracker.SCREEN_NAME ] = name; + + return send( HitType.SCREENVIEW, values ); + } + + /** @inheritDoc */ + public override function event( category:String, action:String, + label:String = "", value:int = -1 ):Boolean + { + if( (category == null) || (category == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "category is empty" ); + } + else + { + return false; + } + } + + if( (action == null) || (action == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "action is empty" ); + } + else + { + return false; + } + } + + var values:Dictionary = new Dictionary(); + + values[ Tracker.EVENT_CATEGORY ] = category; + values[ Tracker.EVENT_ACTION ] = action; + + if( label != "" ) + { + values[ Tracker.EVENT_LABEL ] = label; + } + + /* Note: + Event Value must be non-negative. + it is an integer type so range goes from 0 to MAX_INT. + */ + if( value > -1 ) + { + values[ Tracker.EVENT_VALUE ] = value; + } + + return send( HitType.EVENT, values ); + } + + /** @inheritDoc */ + public override function transaction( id:String, + affiliation:String = "", + revenue:Number = 0, + shipping:Number = 0, + tax:Number = 0, + currency:String = "" ):Boolean + { + if( (id == null) || (id == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "id is empty" ); + } + else + { + return false; + } + } + + var values:Dictionary = new Dictionary(); + + values[ Tracker.TRANSACTION_ID ] = id; + + if( affiliation != "" ) + { + values[ Tracker.TRANSACTION_AFFILIATION ] = affiliation; + } + + values[ Tracker.TRANSACTION_REVENUE ] = revenue; + values[ Tracker.TRANSACTION_SHIPPING ] = shipping; + values[ Tracker.TRANSACTION_TAX ] = tax; + + if( currency != "" ) + { + values[ Tracker.CURRENCY_CODE ] = currency; + } + + return send( HitType.TRANSACTION, values ); + } + + /** @inheritDoc */ + public override function item( transactionId:String, name:String, + price:Number = 0, + quantity:int = 0, + code:String = "", + category:String = "", + currency:String = "" ):Boolean + { + if( (transactionId == null) || (transactionId == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "transaction id is empty" ); + } + else + { + return false; + } + } + + if( (name == null) || (name == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "name is empty" ); + } + else + { + return false; + } + } + + var values:Dictionary = new Dictionary(); + + values[ Tracker.TRANSACTION_ID ] = transactionId; + values[ Tracker.ITEM_NAME ] = name; + + values[ Tracker.ITEM_PRICE ] = price; + values[ Tracker.ITEM_QUANTITY ] = quantity; + + if( code != "" ) + { + values[ Tracker.ITEM_CODE ] = code; + } + + if( category != "" ) + { + values[ Tracker.ITEM_CATEGORY ] = category; + } + + if( currency != "" ) + { + values[ Tracker.CURRENCY_CODE ] = currency; + } + + return send( HitType.ITEM, values ); + } + + /** @inheritDoc */ + public override function social( network:String, action:String, target:String ):Boolean + { + if( (network == null) || (network == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "network is empty" ); + } + else + { + return false; + } + } + + if( (action == null) || (action == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "action is empty" ); + } + else + { + return false; + } + } + + if( (target == null) || (target == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "target is empty" ); + } + else + { + return false; + } + } + + var values:Dictionary = new Dictionary(); + + values[ Tracker.SOCIAL_NETWORK ] = network; + values[ Tracker.SOCIAL_ACTION ] = action; + values[ Tracker.SOCIAL_TARGET ] = target; + + return send( HitType.SOCIAL, values ); + } + + /** @inheritDoc */ + public override function exception( description:String = "", + isFatal:Boolean = true ):Boolean + { + var values:Dictionary = new Dictionary(); + + if( description != "" ) + { + values[ Tracker.EXCEPT_DESCRIPTION ] = description; + } + + if( isFatal ) + { + values[ Tracker.EXCEPT_FATAL ] = "1"; + } + else + { + values[ Tracker.EXCEPT_FATAL ] = "0"; + } + + return send( HitType.EXCEPTION, values ); + } + + /** @inheritDoc */ + public override function timing( category:String, name:String, value:int, + label:String = "", + timinginfo:Dictionary = null ):Boolean + { + if( (category == null) || (category == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "category is empty" ); + } + else + { + return false; + } + } + + if( (name == null) || (name == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "name is empty" ); + } + else + { + return false; + } + } + + if( value < 0 ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "value is empty" ); + } + else + { + return false; + } + } + + var values:Dictionary = new Dictionary(); + + values[ Tracker.USER_TIMING_CATEGORY ] = category; + values[ Tracker.USER_TIMING_VAR ] = name; + values[ Tracker.USER_TIMING_TIME ] = value; + + if( label != "" ) + { + values[ Tracker.USER_TIMING_LABEL ] = label; + } + + if( timinginfo != null ) + { + for( var entry:String in timinginfo ) + { + values[ entry ] = timinginfo[ entry ]; + } + } + + return send( HitType.TIMING, values ); + } + + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/DataSource.as b/src/libraries/uanalytics/tracker/DataSource.as new file mode 100644 index 0000000..2f0fc3c --- /dev/null +++ b/src/libraries/uanalytics/tracker/DataSource.as @@ -0,0 +1,109 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker +{ + + /** + * The DataSource class defines values for the "data source" parameter. + * + *

+ * Those values are examples of what we think is "common use case", + * you can use any value you want. + *

+ * + *

+ * In general, we think you would want to differentiate between + * the web: a SWF hosted in an online HTML page, + * an app: an AIR application, + * and commandline: a Redtamarin command-line program. + *

+ * + *

+ * You may want also to differentiate between + * a desktop app: AIR published for the desktop + * and a mobile app: AIR published for mobile. + *

+ * + *

+ * That said, you could use any values, here few more examples: + * "cms" (content management system), "server" (when you do tracking server-side), + * "shellscript" (when using a shell script vs a static binary), etc. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#DATA_SOURCE Tracker.DATA_SOURCE + * @see libraries.uanalytics.tracking.Metadata Metadata + */ + public class DataSource + { + + /** + * When hit is sent from an online SWF. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const WEB:String = "web"; + + /** + * When hit is sent from an AIR application. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const APP:String = "app"; + + /** + * When hit is sent from a desktop AIR application. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const DESKTOP:String = "desktop"; + + /** + * When hit is sent from a mobile AIR application. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const MOBILE:String = "mobile"; + + /** + * When hit is sent from a Redtamarin command-line program. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const COMMAND_LINE:String = "commandline"; + + /** + * Creates a DataSource. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function DataSource() + { + super(); + } + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/DefaultTracker.as b/src/libraries/uanalytics/tracker/DefaultTracker.as new file mode 100644 index 0000000..78fa20e --- /dev/null +++ b/src/libraries/uanalytics/tracker/DefaultTracker.as @@ -0,0 +1,980 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker +{ + import flash.system.ApplicationDomain; + import flash.utils.Dictionary; + + import libraries.uanalytics.tracker.senders.LoaderHitSender; + import libraries.uanalytics.tracking.Configuration; + import libraries.uanalytics.tracking.HitModel; + import libraries.uanalytics.tracking.HitSampler; + import libraries.uanalytics.tracking.RateLimitError; + import libraries.uanalytics.tracking.RateLimiter; + import libraries.uanalytics.tracking.Tracker; + import libraries.uanalytics.utils.generateUUID; + import libraries.uanalytics.utils.getHostname; + + /** + * Default Google Analytics tracker implementation. + * + *

+ * This tracker is to be considered as a basic common denominator, + * we mainly use it to do quick tests and experiments, + * and you do not want to use it "as is" in production unless you really + * know what you are doing. + *

+ * + *

+ * Important things to know: + *

+ * + * + *

+ * depending on your use case you would want to use a specialised tracker: + * WebTracker, AppTracker, etc. + *

+ * + * @example Usage + * + * import com.google.analytics.tracker.DefaultTracker; + * import com.google.analytics.tracking.Configuration; + * import flash.utils.Dictionary; + * + * // we want to override the default config + * var config:Configuration = new Configuration(); + * //we want to throw errors + * config.enableErrorChecking = true; + * //we want to use a debug sender + * config.senderType = "libraries.uanalytics.tracker.senders.DebugHitSender"; + * + * //store a reference of a dynamic sender otherwise it will not be found + * var sender_tmp:DebugHitSender; + * + * //replace this with a valid Tracking ID + * var trackingID:String = "UA-12345-67"; + * + * /* Unless you store the Client ID somewhere + * each time you will launch this code you will be in a new session + * with a new Client ID. + * If you launch multiple instances of this code at the same time, + * each instance will represent a different tracked user. + * */ + * var tracker:DefaultTracker = new DefaultTracker( trackingID, config ); + * trace( "trackingID: " + tracker.trackingID ); + * trace( " clientID: " + tracker.clientID ); + * + * // if you want to reuse a previous ClientID + * // tracker.set( Tracker.CLIENT_ID, savedClientId ); + * + * // the set() function allow you to set any tracker values + * tracker.set( Tracker.PAGE, "/my default page €" ); + * + * // if you want a particular field to be used only one time + * // once used by a hit request, the hit will be removed + * // tracker.setOneTime( Tracker.NON_INTERACTION, "1" ); + * + * // you can also define a dictionary + * var values:Dictionary = new Dictionary(); + * values[ Tracker.PAGE ] = "/my page €"; //path + * values[ Tracker.TITLE ] = "My Page Title with €"; //title + * + * // those values are temporary and will be used only for this hit + * // temp values will override set() values + * tracker.send( "pageview", values ); + * + * // if you wanted to make them permanent for each hit requests + * // tracker.add( values ); + * + * + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + public class DefaultTracker extends Tracker + { + + /** + * Create a new Tracker with no data model fields set. + * + * @param trackingId the Tracking ID + * @param config the tracker configuration + */ + public function DefaultTracker( trackingId:String = "", + config:Configuration = null ) + { + super(); + + if( !config ) + { + config = new Configuration(); + } + + _config = config; + _ctor( trackingId ); + } + + /** + * @private + * + * We want to move the constructor logic into a method + * so when we inherit it we can replace it + * with other custom builds. + */ + protected function _ctor( trackingId:String = "" ):void + { + _model = new HitModel(); + _temporary = new HitModel(); + + /* Note: + our default safe sender is LoaderHitSender + but we want to be able ot dynamically instantiate + other type of sender with config.senderType + */ + if( _config.senderType != "" ) + { + var S:Class = ApplicationDomain.currentDomain.getDefinition( _config.senderType ) as Class; + _sender = new S( this ); + } + else + { + _sender = new LoaderHitSender( this ); + } + + /* Note: + the tracker starts with 100 hits + that are replenished at a rate of 10 hits per second. + */ + _limiter = new RateLimiter( 100, 10 ); + + if( trackingId != "" ) + { + set( TRACKING_ID, trackingId ); + } + + var cid:String = _getClientID(); + if( cid != "" ) + { + set( CLIENT_ID, cid ); + } + + // no data source by default + } + + /** + * @private + * + * Here we obtain the client id + * in the default implementation we keep it simple + * and each time we will use this tracker we will obtain + * a new clientId. + * + * Ideally we would want to be able to save and reuse + * a clientId, see more advanced implementation. + */ + protected function _getClientID():String + { + return generateUUID(); + } + + /** + * @private + * + * A very basic cache buster. + */ + protected function _getCacheBuster():String + { + var d:Date = new Date(); + var rnd:Number = Math.random(); + return String( d.valueOf() + rnd ); + } + + /** @inheritDoc */ + public override function send( hitType:String = null, tempValues:Dictionary = null ):Boolean + { + /* Note: + yep defensive programming, as trackingId and clientId + are mandatory for a valid hit we really do want them + to be here. + */ + if( (trackingId == "") || (trackingId == null) ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "tracking id is missing." ); + } + else + { + return false; + } + } + + if( (clientId == "") || (clientId == null) ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "client id is missing." ); + } + else + { + return false; + } + } + + /* Note: + We make a copy of our internal data model + and add temporary values (eg. one time values) + and clear up those. + */ + var copy:HitModel = _model.clone(); + copy.add( _temporary ); + + _temporary.clear(); + + /* Note: + If we have user temp values we add them + those will override any other set values + */ + if( tempValues != null ) + { + for( var entry:String in tempValues ) + { + copy.set( entry, tempValues[entry] ); + } + } + + /* Note: + Again defensive programming, we really want the hit type + to be valid, eg. be any of "pageview", "screenview", etc. + */ + if( ((hitType != "") || (hitType != null)) && + HitType.isValid( hitType ) ) + { + copy.set( HIT_TYPE, hitType ); + } + else + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "hit type \"" + hitType + "\" is not valid." ); + } + else + { + return false; + } + } + + /* Note: + we want to be able to sample the hit requests + if Tracker.SAMPLE_RATE = 20.0 + then we want to send only 20% of the hits, not 100% of them + */ + if( _config && _config.enableSampling ) + { + if( HitSampler.isSampled( copy, String(_config.sampleRate) ) ) + { + return false; + } + } + + if( _config && _config.enableThrottling ) + { + if( !_limiter.consumeToken() ) + { + if( _config && _config.enableErrorChecking ) + { + throw new RateLimitError(); + } + else + { + return false; + } + } + } + + if( _config && _config.enableCacheBusting ) + { + copy.set( CACHE_BUSTER, _getCacheBuster() ); + } + + //configuration options + + if( _config && _config.anonymizeIp ) + { + copy.set( ANON_IP, "1" ); + } + + if( _config && (_config.overrideIpAddress != "") ) + { + copy.set( IP_OVERRIDE, _config.overrideIpAddress ); + } + + if( _config && (_config.overrideUserAgent != "") ) + { + copy.set( USER_AGENT_OVERRIDE, _config.overrideUserAgent ); + } + + if( _config && (_config.overrideGeographicalId != "") ) + { + copy.set( GEOGRAPHICAL_OVERRIDE, _config.overrideGeographicalId ); + } + + + var err:Error = null; + + /* Note: + Here we wrap the sender call in a try/catch + to only throw errors if config.enableErrorChecking is enabled + that way by default an error in the analytics does not + break the containing app + also it also allow you to log/capture this error + in your tracker implementation + but most important important if an error occurs + we wan to return false to signal the hit was not sent + */ + try + { + _sender.send( copy ); //can throw Error, IOError, etc. + } + catch( e:Error ) + { + err = e; + } + + if( _config && _config.enableErrorChecking && err ) + { + throw err; + } + + /* Note: + If the HitSender generated an error then we consider + the hit not valid and return false + */ + if( err ) + { + return false; + } + + return true; + } + + /** @inheritDoc */ + public override function pageview( path:String, title:String = "" ):Boolean + { + /* Note: + For 'pageview' hits, + either &dl + or both &dh and &dp have to be specified for the hit to be valid. + + dl - document location - http://foo.com/home?a=b + dh - document hostname - foo.com + dp - document path - /foo + + + Screen Name + This parameter is optional on web properties + On web properties this will default to the unique URL of the page by + either using the &dl parameter as-is + or assembling it from &dh and &dp. + cd - screen name - High Scores + + ---- + v=1 // Version. + &tid=UA-XXXXX-Y // Tracking ID / Property ID. + &cid=555 // Anonymous Client ID. + + &t=pageview // Pageview hit type. + &dh=mydemo.com // Document hostname. + &dp=/home // Page. + &dt=homepage // Title. + */ + + if( (path == null) || (path == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "path is empty" ); + } + else + { + return false; + } + + } + + var values:Dictionary = new Dictionary(); + values[ Tracker.DOCUMENT_PATH ] = path; + + if( title && (title.length > 0) ) + { + if( title.length > 1500 ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "Title is bigger than 1500 bytes." ); + } + else + { + return false; + } + } + + values[ Tracker.DOCUMENT_TITLE ] = title; + } + + /* Note: + by default we will fetch the hostname and at worst + this one will be "localhost" if it can not be found. + + eg. a pageview hit + with dh=localhost is valid + without dh= is invalid + */ + var hostname:String = get( Tracker.DOCUMENT_HOSTNAME ); + if( hostname == null ) + { + if( !(Tracker.DOCUMENT_HOSTNAME in values) ) + { + hostname = getHostname(); + + if( hostname == "" ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "hostname is not defined." ); + } + else + { + return false; + } + } + else + { + values[ Tracker.DOCUMENT_HOSTNAME ] = hostname; + } + } + } + + return send( HitType.PAGEVIEW, values ); + } + + /** @inheritDoc */ + public override function screenview( name:String, appinfo:Dictionary = null ):Boolean + { + /* Note: + Application Name + This field is required for all hit types sent to app properties. + an - application name - My App + + Screen Name + required on mobile properties for screenview hits, + where it is used for the 'Screen Name' of the screenview hit. + cd - screen name - High Scores + + ---- + v=1 // Version. + &tid=UA-XXXXX-Y // Tracking ID / Property ID. + &cid=555 // Anonymous Client ID. + + &t=screenview // Screenview hit type. + &an=funTimes // App name. + &av=4.2.0 // App version. + &aid=com.foo.App // App Id. + &aiid=com.android.vending // App Installer Id. + + &cd=Home // Screen name / content description. + */ + + if( (name == null) || (name == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "name is empty" ); + } + else + { + return false; + } + } + + var values:Dictionary = new Dictionary(); + if( appinfo != null ) + { + for( var entry:String in appinfo ) + { + values[ entry ] = appinfo[ entry ]; + } + } + + var app_name:String = get( Tracker.APP_NAME ); + if( app_name == null ) + { + if( !(Tracker.APP_NAME in values) ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "Application name is not defined." ); + } + else + { + return false; + } + } + } + + values[ Tracker.SCREEN_NAME ] = name; + + return send( HitType.SCREENVIEW, values ); + } + + /** @inheritDoc */ + public override function event( category:String, action:String, + label:String = "", value:int = -1 ):Boolean + { + /* Note: + + Event Category + Required for event hit type. + Specifies the event category. Must not be empty. + ec - event category - Category + + Event Action + Required for event hit type. + Specifies the event action. Must not be empty. + ea - event action - Action + + ---- + v=1 // Version. + &tid=UA-XXXXX-Y // Tracking ID / Property ID. + &cid=555 // Anonymous Client ID. + + &t=event // Event hit type + &ec=video // Event Category. Required. + &ea=play // Event Action. Required. + &el=holiday // Event label. + &ev=300 // Event value. + */ + + if( (category == null) || (category == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "category is empty" ); + } + else + { + return false; + } + } + + if( (action == null) || (action == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "action is empty" ); + } + else + { + return false; + } + } + + var values:Dictionary = new Dictionary(); + + values[ Tracker.EVENT_CATEGORY ] = category; + values[ Tracker.EVENT_ACTION ] = action; + + if( label != "" ) + { + values[ Tracker.EVENT_LABEL ] = label; + } + + /* Note: + Event Value must be non-negative. + it is an integer type so range goes from 0 to MAX_INT. + */ + if( value > -1 ) + { + values[ Tracker.EVENT_VALUE ] = value; + } + + return send( HitType.EVENT, values ); + } + + /** @inheritDoc */ + public override function transaction( id:String, + affiliation:String = "", + revenue:Number = 0, + shipping:Number = 0, + tax:Number = 0, + currency:String = "" ):Boolean + { + /* Note: + + + ---- + v=1 // Version. + &tid=UA-XXXXX-Y // Tracking ID / Property ID. + &cid=555 // Anonymous Client ID. + + &t=transaction // Transaction hit type. + &ti=12345 // transaction ID. Required. + &ta=westernWear // Transaction affiliation. + &tr=50.00 // Transaction revenue. + &ts=32.00 // Transaction shipping. + &tt=12.00 // Transaction tax. + &cu=EUR // Currency code. + */ + + if( (id == null) || (id == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "id is empty" ); + } + else + { + return false; + } + } + + var values:Dictionary = new Dictionary(); + + values[ Tracker.TRANSACTION_ID ] = id; + + if( affiliation != "" ) + { + values[ Tracker.TRANSACTION_AFFILIATION ] = affiliation; + } + + values[ Tracker.TRANSACTION_REVENUE ] = revenue; + values[ Tracker.TRANSACTION_SHIPPING ] = shipping; + values[ Tracker.TRANSACTION_TAX ] = tax; + + if( currency != "" ) + { + values[ Tracker.CURRENCY_CODE ] = currency; + } + + return send( HitType.TRANSACTION, values ); + } + + /** @inheritDoc */ + public override function item( transactionId:String, name:String, + price:Number = 0, + quantity:int = 0, + code:String = "", + category:String = "", + currency:String = "" ):Boolean + { + /* Note: + + + ---- + v=1 // Version. + &tid=UA-XXXXX-Y // Tracking ID / Property ID. + &cid=555 // Anonymous Client ID. + + &t=item // Item hit type. + &ti=12345 // Transaction ID. Required. + &in=sofa // Item name. Required. + &ip=300 // Item price. + &iq=2 // Item quantity. + &ic=u3eqds43 // Item code / SKU. + &iv=furniture // Item variation / category. + &cu=EUR // Currency code. + */ + + if( (transactionId == null) || (transactionId == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "transaction id is empty" ); + } + else + { + return false; + } + } + + if( (name == null) || (name == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "name is empty" ); + } + else + { + return false; + } + } + + var values:Dictionary = new Dictionary(); + + values[ Tracker.TRANSACTION_ID ] = transactionId; + values[ Tracker.ITEM_NAME ] = name; + + values[ Tracker.ITEM_PRICE ] = price; + values[ Tracker.ITEM_QUANTITY ] = quantity; + + if( code != "" ) + { + values[ Tracker.ITEM_CODE ] = code; + } + + if( category != "" ) + { + values[ Tracker.ITEM_CATEGORY ] = category; + } + + if( currency != "" ) + { + values[ Tracker.CURRENCY_CODE ] = currency; + } + + return send( HitType.ITEM, values ); + } + + /** @inheritDoc */ + public override function social( network:String, action:String, target:String ):Boolean + { + /* Note: + + Social Network + Required for social hit type. + Specifies the social network, for example Facebook or Google Plus. + sn - social network - twitter + + Social Action + Required for social hit type. + Specifies the social interaction action. For example on Google Plus + when a user clicks the +1 button, the social action is 'plus'. + sa - social action - share + + Social Action Target + Required for social hit type. + Specifies the target of a social interaction. + This value is typically a URL but can be any text. + st - social target - http://foo.com + + + ---- + v=1 // Version. + &tid=UA-XXXXX-Y // Tracking ID / Property ID. + &cid=555 // Anonymous Client ID. + + &t=social // Social hit type. + &sa=like // Social Action. Required. + &sn=facebook // Social Network. Required. + &st=/home // Social Target. Required. + */ + + if( (network == null) || (network == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "network is empty" ); + } + else + { + return false; + } + } + + if( (action == null) || (action == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "action is empty" ); + } + else + { + return false; + } + } + + if( (target == null) || (target == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "target is empty" ); + } + else + { + return false; + } + } + + var values:Dictionary = new Dictionary(); + + values[ Tracker.SOCIAL_NETWORK ] = network; + values[ Tracker.SOCIAL_ACTION ] = action; + values[ Tracker.SOCIAL_TARGET ] = target; + + return send( HitType.SOCIAL, values ); + } + + /** @inheritDoc */ + public override function exception( description:String = "", + isFatal:Boolean = true ):Boolean + { + /* Note: + + + ---- + v=1 // Version. + &tid=UA-XXXXX-Y // Tracking ID / Property ID. + &cid=555 // Anonymous Client ID. + + &t=exception // Exception hit type. + &exd=IOException // Exception description. + &exf=1 // Exception is fatal? + */ + + var values:Dictionary = new Dictionary(); + + if( description != "" ) + { + values[ Tracker.EXCEPT_DESCRIPTION ] = description; + } + + if( isFatal ) + { + values[ Tracker.EXCEPT_FATAL ] = "1"; + } + else + { + values[ Tracker.EXCEPT_FATAL ] = "0"; + } + + return send( HitType.EXCEPTION, values ); + } + + /** @inheritDoc */ + public override function timing( category:String, name:String, value:int, + label:String = "", + timinginfo:Dictionary = null ):Boolean + { + /* Note: + + User timing category + Required for timing hit type. + Specifies the user timing category. + utc - user timing category - category + + User timing variable name + Required for timing hit type. + Specifies the user timing variable. + utv - user timing variable - lookup + + User timing time + Required for timing hit type. + Specifies the user timing value. The value is in milliseconds. + utt - user timing time - 123 + + User timing label + Optional. + utl - user timing label - label + + ---- + v=1 // Version. + &tid=UA-XXXXX-Y // Tracking ID / Property ID. + &cid=555 // Anonymous Client ID. + + &t=timing // Timing hit type. + &utc=jsonLoader // Timing category. + &utv=load // Timing variable. + &utt=5000 // Timing time. + &utl=jQuery // Timing label. + + // These values are part of browser load times + + &dns=100 // DNS load time. + &pdt=20 // Page download time. + &rrt=32 // Redirect time. + &tcp=56 // TCP connect time. + &srt=12 // Server response time. + */ + + if( (category == null) || (category == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "category is empty" ); + } + else + { + return false; + } + } + + if( (name == null) || (name == "") ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "name is empty" ); + } + else + { + return false; + } + } + + if( value < 0 ) + { + if( _config && _config.enableErrorChecking ) + { + throw new ArgumentError( "value is empty" ); + } + else + { + return false; + } + } + + var values:Dictionary = new Dictionary(); + + values[ Tracker.USER_TIMING_CATEGORY ] = category; + values[ Tracker.USER_TIMING_VAR ] = name; + values[ Tracker.USER_TIMING_TIME ] = value; + + if( label != "" ) + { + values[ Tracker.USER_TIMING_LABEL ] = label; + } + + if( timinginfo != null ) + { + for( var entry:String in timinginfo ) + { + values[ entry ] = timinginfo[ entry ]; + } + } + + return send( HitType.TIMING, values ); + } + + } + +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/HitType.as b/src/libraries/uanalytics/tracker/HitType.as new file mode 100644 index 0000000..e255272 --- /dev/null +++ b/src/libraries/uanalytics/tracker/HitType.as @@ -0,0 +1,155 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker +{ + import flash.system.System; + import flash.utils.describeType; + + /** + * The HitType class defines allowed values for the "hit type" parameter. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#send() Tracker.send() + * @see libraries.uanalytics.tracking.Tracker#HIT_TYPE Tracker.HIT_TYPE + * @see libraries.uanalytics.tracking.Metadata Metadata + * @see http://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#t Hit Type + */ + public class HitType + { + + /** + * Defines a "pageview" hit type. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const PAGEVIEW:String = "pageview"; + + /** + * Defines a "screenview" hit type. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const SCREENVIEW:String = "screenview"; + + /** + * Defines an "event" hit type. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const EVENT:String = "event"; + + /** + * Defines a "transaction" hit type. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const TRANSACTION:String = "transaction"; + + /** + * Defines an "item" hit type. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const ITEM:String = "item"; + + /** + * Defines a "social" hit type. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const SOCIAL:String = "social"; + + /** + * Defines an "exception" hit type. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const EXCEPTION:String = "exception"; + + /** + * Defines a "timing" hit type. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const TIMING:String = "timing"; + + /** + * Returns true is the passed string type + * is a valid Hit Type. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static function isValid( type:String ):Boolean + { + var _class:XML = describeType( HitType ); + var found:Boolean = false; + + var property:String; + for each( var member:XML in _class.constant ) + { + property = String( member.@name ); + + if( HitType[ property ] == type ) + { + found = true; + break; + } + + } + System.disposeXML( _class ); + + if( found ) + { + return true; + } + + return false; + } + + /** + * Creates a HitType. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function HitType() + { + super(); + } + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/SessionControl.as b/src/libraries/uanalytics/tracker/SessionControl.as new file mode 100644 index 0000000..6b67627 --- /dev/null +++ b/src/libraries/uanalytics/tracker/SessionControl.as @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker +{ + + /** + * The SessionControl class defines allowed values for the + * "session control" parameter. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * see: libraries.uanalytics.tracking.Tracker#SESSION_CONTROL Tracker.SESSION_CONTROL + * @see libraries.uanalytics.tracking.Metadata Metadata + * @see http://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#sc Session Control + */ + public class SessionControl + { + + /** + * Forces a new session to start with the hit request. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const START:String = "start"; + + /** + * Forces the current session to end with the hit request. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const END:String = "end"; + + /** + * Creates a SessionControl. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function SessionControl() + { + super(); + } + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/SystemInfo.as b/src/libraries/uanalytics/tracker/SystemInfo.as new file mode 100644 index 0000000..7eb20f5 --- /dev/null +++ b/src/libraries/uanalytics/tracker/SystemInfo.as @@ -0,0 +1,203 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker +{ + import flash.utils.Dictionary; + + import libraries.uanalytics.tracking.Tracker; + + /** + * The SystemInfo defines parameters meant to be used + * for System Information Tracking. + * + *

+ * By default, all the parameters are empty (not initialised). + * Only the non-empty (or initialised) parameters wil be exported + * by the toDictionary() function. + *

+ * + * @example Usage + * + * // define the system info properties manually + * var sysinfo:SystemInfo = new SystemInfo(); + * sysinfo.userLanguage = "en"; + * sysinfo.documentEncoding = "UTF-8"; + * sysinfo.screenResolution = "800x600"; + * + * // pass it to the tracker + * tracker.add( sysinfo.toDictionary() ); + * + * + * + * // automatically generate the system info properties + * var sysinfo:SystemInfo = generateFlashSystemInfo(); + * + * // pass it to the tracker + * tracker.add( sysinfo.toDictionary() ); + * + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.utils#generateFlashSystemInfo() generateFlashSystemInfo() + * @see libraries.uanalytics.utils#generateAIRSystemInfo() generateAIRSystemInfo() + * @see libraries.uanalytics.utils#generateCLISystemInfo() generateCLISystemInfo() + */ + public class SystemInfo + { + + /** + * The screen resolution. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#SCREEN_RESOLUTION Tracker.SCREEN_RESOLUTION + */ + public var screenResolution:String = ""; + + /** + * The viewable area of the browser / device. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#VIEWPORT_SIZE Tracker.VIEWPORT_SIZE + */ + public var viewportSize:String = ""; + + /** + * The character set used to encode the page / document. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#DOCUMENT_ENCODING Tracker.DOCUMENT_ENCODING + */ + public var documentEncoding:String = ""; + + /** + * The screen color depth. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#SCREEN_COLORS Tracker.SCREEN_COLORS + */ + public var screenColors:String = ""; + + /** + * The language. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#USER_LANGUAGE Tracker.USER_LANGUAGE + */ + public var userLanguage:String = ""; + + /** + * Whether Java is enabled. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#JAVA_ENABLED Tracker.JAVA_ENABLED + */ + public var javaEnabled:Boolean = false; + + /** + * The flash version. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#FLASH_VERSION Tracker.FLASH_VERSION + */ + public var flashVersion:String = ""; + + /** + * Creates an empty SystemInfo. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function SystemInfo() + { + super(); + } + + /** + * Export the initialised (non-empty) parameters. + * + * @return a Dictionary containing field/value pairs. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function toDictionary():Dictionary + { + var values:Dictionary = new Dictionary(); + + if( screenResolution.length > 0 ) + { + values[ Tracker.SCREEN_RESOLUTION ] = screenResolution; + } + + if( viewportSize.length > 0 ) + { + values[ Tracker.VIEWPORT_SIZE ] = viewportSize; + } + + if( documentEncoding.length > 0 ) + { + values[ Tracker.DOCUMENT_ENCODING ] = documentEncoding; + } + + if( screenColors.length > 0 ) + { + values[ Tracker.SCREEN_COLORS ] = screenColors; + } + + if( userLanguage.length > 0 ) + { + values[ Tracker.USER_LANGUAGE ] = userLanguage; + } + + if( javaEnabled ) + { + values[ Tracker.JAVA_ENABLED ] = "1"; + } + + if( flashVersion.length > 0 ) + { + values[ Tracker.FLASH_VERSION ] = flashVersion; + } + + return values; + } + + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/TimingInfo.as b/src/libraries/uanalytics/tracker/TimingInfo.as new file mode 100644 index 0000000..bbfa811 --- /dev/null +++ b/src/libraries/uanalytics/tracker/TimingInfo.as @@ -0,0 +1,215 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker +{ + import flash.utils.Dictionary; + + import libraries.uanalytics.tracking.Tracker; + + /** + * The TimingInfo defines parameters meant to be used + * for the Timing Tracking. + * + *

+ * By default, all the parameters are empty (not initialised). + * Only the non-empty (or initialised) parameters wil be exported + * by the toDictionary() function. + *

+ * + * @example Usage + * + * // define the timing info properties manually + * var timeinfo:TimingInfo = new TimingInfo(); + * timeinfo.tcpConnectTime = 100; + * timeinfo.pageLoadTime = 500; + * + * // pass it to the tracker + * tracker.add( timeinfo.toDictionary() ); + * + * // OR pass it only for the current hit + * tracker.timing( "timing", "application_load", 100, "", timeinfo.toDictionary() ); + * + * + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.utils#generateAIRAppInfo() generateAIRAppInfo() + */ + public class TimingInfo + { + + /** + * The time it took for a page to load. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#PAGE_LOAD_TIME Tracker.PAGE_LOAD_TIME + */ + public var pageLoadTime:int = -1; + + /** + * The time it took to do a DNS lookup. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#DNS_TIME Tracker.DNS_TIME + */ + public var dnsTime:int = -1; + + /** + * The time it took for the page to be downloaded. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#PAGE_DOWNLOAD_TIME Tracker.PAGE_DOWNLOAD_TIME + */ + public var pageDownloadTime:int = -1; + + /** + * The time it took for any redirects to happen. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#REDIRECT_RESPONSE_TIME Tracker.REDIRECT_RESPONSE_TIME + */ + public var redirectResponseTime:int = -1; + + /** + * The time it took for a TCP connection to be made. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#TCP_CONNECT_TIME Tracker.TCP_CONNECT_TIME + */ + public var tcpConnectTime:int = -1; + + /** + * The time it took for the server to respond after the + * connect time. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#TIME Tracker.TIME + */ + public var serverResponseTime:int = -1; + + /** + * The time it took for Document.readyState + * to be 'interactive'. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#DOM_INTERACTIVE_TIME Tracker.DOM_INTERACTIVE_TIME + */ + public var domInteractiveTime:int = -1; + + /** + * The time it took for the DOMContentLoaded Event + * to fire. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracking.Tracker#CONTENT_LOAD_TIME Tracker.CONTENT_LOAD_TIME + */ + public var contentLoadTime:int = -1; + + /** + * Creates an empty TimingInfo. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function TimingInfo() + { + super(); + } + + /** + * Export the initialised (non-empty) parameters. + * + * @return a Dictionary containing field/value pairs. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function toDictionary():Dictionary + { + var values:Dictionary = new Dictionary(); + + if( pageLoadTime > -1 ) + { + values[ Tracker.PAGE_LOAD_TIME ] = String(pageLoadTime); + } + + if( dnsTime > -1 ) + { + values[ Tracker.DNS_TIME ] = String(dnsTime); + } + + if( pageDownloadTime > -1 ) + { + values[ Tracker.PAGE_DOWNLOAD_TIME ] = String(pageDownloadTime); + } + + if( redirectResponseTime > -1 ) + { + values[ Tracker.REDIRECT_RESPONSE_TIME ] = String(redirectResponseTime); + } + + if( tcpConnectTime > -1 ) + { + values[ Tracker.TCP_CONNECT_TIME ] = String(tcpConnectTime); + } + + if( serverResponseTime > -1 ) + { + values[ Tracker.SERVER_RESPONSE_TIME ] = String(serverResponseTime); + } + + if( domInteractiveTime > -1 ) + { + values[ Tracker.DOM_INTERACTIVE_TIME ] = String(domInteractiveTime); + } + + if( contentLoadTime > -1 ) + { + values[ Tracker.CONTENT_LOAD_TIME ] = String(contentLoadTime); + } + + return values; + } + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/WebTracker.as b/src/libraries/uanalytics/tracker/WebTracker.as new file mode 100644 index 0000000..8f24edc --- /dev/null +++ b/src/libraries/uanalytics/tracker/WebTracker.as @@ -0,0 +1,192 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker +{ + import flash.events.NetStatusEvent; + import flash.net.SharedObject; + import flash.net.SharedObjectFlushStatus; + import flash.system.ApplicationDomain; + + import libraries.uanalytics.tracker.senders.LoaderHitSender; + import libraries.uanalytics.tracking.Configuration; + import libraries.uanalytics.tracking.HitModel; + import libraries.uanalytics.tracking.RateLimiter; + import libraries.uanalytics.tracking.Tracker; + import libraries.uanalytics.utils.generateUUID; + + /** + * A tracker specialised for the web. + * + *

+ * The equivalent of analytics.js for online SWF files. + *

+ *
+     * analytics.js:
+     * Each analytics.js tracker object starts with 20 hits that are replenished
+     * at a rate of 2 hit per second. Applies to all hits except for ecommerce
+     * (item or transaction).
+     * 
+ * + *

+ * Features: + *

+ * + * + * @example Usage + * + * import com.google.analytics.tracker.WebTracker; + * + * var tracker:WebTracker = new WebTracker( "UA-12345-67" ); + * + * tracer.pageview( "/hello/world", "Hello World" ); + * + * + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://developers.google.com/analytics/devguides/collection/protocol/v1/limits-quotas Limits and Quotas + */ + public class WebTracker extends DefaultTracker + { + protected var _storage:SharedObject; + + public function WebTracker( trackingId:String = "", + config:Configuration = null ) + { + super( trackingId, config ); + } + + /** + * @private + * + * Here the slight differences with the DefaultTracker + * + */ + protected override function _ctor( trackingId:String = "" ):void + { + _model = new HitModel(); + _temporary = new HitModel(); + + if( _config.senderType != "" ) + { + var S:Class = ApplicationDomain.currentDomain.getDefinition( _config.senderType ) as Class; + _sender = new S( this ); + } + else + { + _sender = new LoaderHitSender( this ); + } + + /* Note: + the tracker starts with 20 hits + that are replenished at a rate of 2 hits per second. + */ + _limiter = new RateLimiter( 20, 2, 1 ); + + if( trackingId != "" ) + { + set( TRACKING_ID, trackingId ); + } + + var cid:String = _getClientID(); + if( cid != "" ) + { + set( CLIENT_ID, cid ); + } + + set( Tracker.DATA_SOURCE, DataSource.WEB ); + } + + /** + * @private + * + * A storage mecanism based on SharedObject + * to save and/or restore the ClientId. + */ + protected override function _getClientID():String + { + // Load the SharedObject '_ga' + _storage = SharedObject.getLocal( _config.storageName ); + var cid:String; + + if( !_storage.data.clientid ) + { + // CID not found, generate Client ID + cid = generateUUID(); + + // Save CID into SharedObject + _storage.data.clientid = cid; + + var flushStatus:String = null; + try + { + flushStatus = _storage.flush( 1024 ); //1KB + } + catch( e:Error ) + { + //trace( "Could not write SharedObject to disk: " + e.message ); + } + + if( flushStatus != null ) + { + switch( flushStatus ) + { + case SharedObjectFlushStatus.PENDING: + // Requesting permission to save object... + _storage.addEventListener( NetStatusEvent.NET_STATUS, onFlushStatus ); + break; + + case SharedObjectFlushStatus.FLUSHED: + // Value flushed to disk" + break; + } + } + + } + else + { + // CID found, restore from SharedObject + cid = _storage.data.clientid; + } + + return cid; + } + + protected function onFlushStatus( event:NetStatusEvent ):void + { + // User closed permission dialog... + _storage.removeEventListener( NetStatusEvent.NET_STATUS, onFlushStatus); + + switch( event.info.code ) + { + case "SharedObject.Flush.Success": + // User granted permission, value saved + break; + + case "SharedObject.Flush.Failed": + // User denied permission, value not saved + break; + } + } + + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/addons/DebugFileSystemStorage.as b/src/libraries/uanalytics/tracker/addons/DebugFileSystemStorage.as new file mode 100644 index 0000000..c880652 --- /dev/null +++ b/src/libraries/uanalytics/tracker/addons/DebugFileSystemStorage.as @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker.addons +{ + import C.errno.*; + import C.sys.stat.*; + import C.unistd.*; + import C.stdio.*; + + import flash.utils.ByteArray; + + import libraries.uanalytics.tracking.Configuration; + + import shell.FileSystem; + + /** + * Utility function to debug and/or reset the File System storage. + * + * @param show show informations and datas of the sotrage (default to true). + * @param reset clear the storage from its data (default to false). + * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function DebugFileSystemStorage( show:Boolean = true, + reset:Boolean = false ):void + { + var homedir:String = FileSystem.homeDirectory; + + if( homedir == "" ) + { + homedir = "/"; // root dir + } + + var exists:int = access( homedir, F_OK ); + if( exists < 0 ) + { + throw new CError( errno ); + } + + var canread:int = access( homedir, R_OK ); + if( canread < 0 ) + { + throw new CError( errno ); + } + + var canwrite:int = access( homedir, W_OK ); + if( canwrite < 0 ) + { + throw new CError( errno ); + } + + // by that point we are sure the HOME directory exists and is readable/writable + // for ex: /home/username + + var savepath:String = homedir + "/.uanalytics"; + var s_exists:int = access( savepath, F_OK ); + if( s_exists < 0 ) + { + throw new CError( errno ); + } + + // by that point we are sure our preference dir .uanalytics exists + // for ex: /home/username/.uanalytics + + var config:Configuration = new Configuration(); + var filepath:String = savepath + "/" + config.storageName; + var data:String = ""; + + var f_exists:int = access( filepath, F_OK ); + if( f_exists < 0 ) + { + throw new CError( errno ); + } + else + { + // the save file already exists + data = FileSystem.read( filepath ); + } + + if( show ) + { + trace( "File: " + filepath ); + trace( "size: " + data.length + " bytes" ); + trace( "data:" ); + trace( " |_ " + data ); + } + + if( reset ) + { + // bug: does not allow you to write an empty file + //FileSystem.write( filepath, "" ); + var fp:FILE = fopen( filepath, "w" ); + if( fp ) + { + var bytes:ByteArray = new ByteArray(); + fwrite( bytes, bytes.length, fp ); + fflush( fp ); + bytes.clear(); + fclose( fp ); + } + } + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/addons/DebugSharedObjectStorage.as b/src/libraries/uanalytics/tracker/addons/DebugSharedObjectStorage.as new file mode 100644 index 0000000..cfbbbed --- /dev/null +++ b/src/libraries/uanalytics/tracker/addons/DebugSharedObjectStorage.as @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker.addons +{ + import flash.net.SharedObject; + + import libraries.uanalytics.tracking.Configuration; + + /** + * Utility function to debug and/or reset the SharedObject + * storage. + * + * @param show show informations and datas of the sotrage (default to true). + * @param reset clear the storage from its data (default to false). + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + public function DebugSharedObjectStorage( show:Boolean = true, + reset:Boolean = false ):void + { + var conf:Configuration = new Configuration(); + var so:SharedObject = SharedObject.getLocal( conf.storageName ); + + if( show ) + { + trace( "SharedObject: " + conf.storageName ); + trace( "size: " + so.size + " bytes" ); + trace( "data:" ); + for( var m:String in so.data ) + { + trace( " |_ " + m + ": " + so.data[m] ); + } + } + + if( reset ) + { + so.clear(); + } + + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/addons/Twitter.as b/src/libraries/uanalytics/tracker/addons/Twitter.as new file mode 100644 index 0000000..188c3f8 --- /dev/null +++ b/src/libraries/uanalytics/tracker/addons/Twitter.as @@ -0,0 +1,208 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker.addons +{ + import libraries.uanalytics.tracking.AnalyticsTracker; + + /** + * Helper class to send Twitter social hits. + * + *

+ * To integrate with a Twitter API. + *

+ * + * @example Usage + * + * var tracker:AppTracker = new AppTracker( "UA-12345-67" ); + * var twitter:Twitter = new Twitter( tracker ); + * + * //... + * + * private function onClick( event:MouseEvent ):void + * { + * var sent:Boolean = twitter.retweet( this.url ); + * trace( "your retweet has been tracked" ); + * } + * + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public class Twitter + { + + /** + * The Twitter network definition. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const NETWORK:String = "Twitter"; + + + /** + * The Click action. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const CLICK:String = "Click"; + + /** + * The Tweet action. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const TWEET:String = "Tweet"; + + /** + * The Retweet action. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const RETWEET:String = "Retweet"; + + /** + * The Star action. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const STAR:String = "Star"; + + /** + * The Follow action. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const FOLLOW:String = "Follow"; + + + private var _tracker:AnalyticsTracker; + + /** + * Create a Twitter social hits helper. + * + * @param tracker the tracker that will send the social hits. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function Twitter( tracker:AnalyticsTracker ) + { + super(); + _tracker = tracker; + } + + /** + * The action to click on a Twitter button. + * + *

+ * Some example of buttons: Tweet, Follow, hashtag, Mention, etc. + *

+ * + * @param target the target of the social interaction. + * @return false if the social hit has not been sent due to sampling + * or other reasons. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function click( target:String ):Boolean + { + return _tracker.social( NETWORK, CLICK, target ); + } + + /** + * The action of publishing a tweet. + * + * @param target the target of the social interaction. + * @return false if the social hit has not been sent due to sampling + * or other reasons. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function tweet( target:String ):Boolean + { + return _tracker.social( NETWORK, TWEET, target ); + } + + /** + * The action to retweet a tweet. + * + * @param target the target of the social interaction. + * @return false if the social hit has not been sent due to sampling + * or other reasons. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function retweet( target:String ):Boolean + { + return _tracker.social( NETWORK, RETWEET, target ); + } + + /** + * The action to favorite a tweet. + * + * @param target the target of the social interaction. + * @return false if the social hit has not been sent due to sampling + * or other reasons. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function star( target:String ):Boolean + { + return _tracker.social( NETWORK, STAR, target ); + } + + /** + * The action to follow a twitter user. + * + * @param target the target of the social interaction. + * @return false if the social hit has not been sent due to sampling + * or other reasons. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function follow( target:String ):Boolean + { + return _tracker.social( NETWORK, FOLLOW, target ); + } + + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/senders/BSDSocketHitSender.as b/src/libraries/uanalytics/tracker/senders/BSDSocketHitSender.as new file mode 100644 index 0000000..799de21 --- /dev/null +++ b/src/libraries/uanalytics/tracker/senders/BSDSocketHitSender.as @@ -0,0 +1,625 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker.senders +{ + import C.arpa.inet.*; + import C.errno.*; + import C.netdb.*; + import C.netinet.*; + import C.sys.socket.*; + import C.unistd.*; + + import flash.utils.ByteArray; + + import libraries.uanalytics.tracking.AnalyticsTracker; + import libraries.uanalytics.tracking.HitModel; + import libraries.uanalytics.tracking.HitSender; + import libraries.uanalytics.utils.generateCLIUserAgent; + + /** + * A simple implementation using BSD Sockets that will send hit data + * on the command-line to Google Analytics servers. + * + *

+ * This is the default implementation we use in all the Redtamarin trackers. + *

+ * + * + * It will work everywhere Redtamarin can run: Windows, Mac OS X, and Linux. + * From inside ABC files, SWF files, static binaries and shell scripts. + * + * + *

+ * Different and More complicated implementations could do more things; + * for example: queue hits and send them at a later time, validate the hit + * by checking the HTTP response code, etc. + *

+ * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public class BSDSocketHitSender extends HitSender + { + private static const _CRLF:String = "\r\n"; // CRLF 0x0D 0x0A + + /** + * The default HTTP port (80). + * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const DEFAULT_PORT:int = 80; + + /** + * The default HTTPS port (443). + * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const SECURE_PORT:int = 443; + + /** + * An Analytics tracker. + * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected var _tracker:AnalyticsTracker; + + private var _enableErrorChecking:Boolean; + private var _debug:Boolean; + private var _verbose:Boolean; + + private var _connected:Boolean; + + private var _addrlist:Array; + private var _sockfd:int; + private var _info:addrinfo; + + private var _localAddress:String; + private var _localPort:int; + + private var _remoteAddress:String; + private var _remotePort:int; + + private var _remoteAddresses:Array; + + /** + * Creates a BSDSocketHitSender. + * + * @param tracker an analytics tracker + * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function BSDSocketHitSender( tracker:AnalyticsTracker ) + { + super(); + _tracker = tracker; + + _enableErrorChecking = false; + _debug = false; + _verbose = false; + _reset(); + } + + /** + * Function hook for the socket "connect" event. + * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public var onConnect:Function;/* = function():void + { + trace( "onConnect()" ); + }*/ + + /** + * Function hook for the socket "disconnect" event. + * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public var onDisconnect:Function;/* = function():void + { + trace( "onDisconnect()" ); + }*/ + + /** + * Function hook for the socket "send" event. + * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public var onSend:Function;/* = function():void + { + trace( "onSend( bytes:ByteArray, total:int )" ); + }*/ + + /** + * Function hook for the socket "receive" event. + * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public var onReceive:Function;/* = function():void + { + trace( "onReceive( bytes:ByteArray, total:int )" ); + }*/ + + private function _reset():void + { + _connected = false; + + _addrlist = null; + _sockfd = -1; + _info = null; + + _localAddress = ""; + _localPort = -1; + _remoteAddress = ""; + _remotePort = -1; + + _remoteAddresses = null; + } + + private function _open( hostname:String, port:String ):int + { + var hints:addrinfo = new addrinfo(); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + + var eaierr:CEAIrror = new CEAIrror(); + var addrlist:Array = getaddrinfo( hostname, port, hints, eaierr ); + + if( !addrlist ) + { + if( enableErrorChecking ) { throw eaierr; } + + return -1; + } + + _addrlist = addrlist; + + var i:uint; + var info:addrinfo; + var sockfd:int; + var result:int; + + for( i = 0; i < addrlist.length; i++ ) + { + info = addrlist[i]; + + sockfd = socket( info.ai_family, info.ai_socktype, info.ai_protocol ); + if( sockfd < 0 ) + { + var e:CError = new CError( "", errno ); + if( enableErrorChecking ) { throw e; } + + return -1; + } + + result = connect( sockfd, info.ai_addr ); + if( result < 0 ) + { + var e:CError = new CError( "", errno ); + if( enableErrorChecking ) { throw e; } + + return -1; + } + break; + } + + _info = info; + + var a:String = inet_ntop( _info.ai_family, _info.ai_addr ); + if( !a ) + { + var e:CError = new CError( "", errno ); + if( enableErrorChecking ) { throw e; } + } + else + { + _remoteAddress = a; + } + + _remotePort = ntohs( _info.ai_addr.sin_port ); + + return sockfd; + } + + private function _findLocalAddressAndPort():void + { + var addr:sockaddr_in = new sockaddr_in(); + + var result:int = getsockname( _sockfd, addr ); + if( result == -1 ) + { + var e:CError = new CError( "", errno ); + if( enableErrorChecking ) { throw e; } + } + else + { + var a:String = inet_ntop( addr.sin_family, addr ); + if( !a ) + { + var e:CError = new CError( "", errno ); + if( enableErrorChecking ) { throw e; } + } + else + { + _localAddress = a; + } + + _localPort = ntohs( addr.sin_port ); + } + } + + private function _receiveAll( socket:int, bytes:ByteArray, len:int = 8192, flags:int = 0 ):int + { + var total:uint = 0; // how many bytes we received + var n:int; + var b:ByteArray = new ByteArray(); + + var run:Boolean = true; + while( run ) + { + b.clear(); + n = recv( socket, b, len, flags ); + + if( n == -1 ) { run = false; break; } + bytes.writeBytes( b ); + total += n; + if( n == 0 ) { run = false; break; } + } + + b.clear(); + + if( n < 0 ) + { + return -1; //failure + } + + return total; // number of bytes actually received + } + + /** + * Specifies whether errors encountered by the sockets are reported + * to the application. + * + *

+ * When enableErrorChecking is true methods are synchronous + * and can throw errors. When enableErrorChecking is false, + * the default, the methods are asynchronous and errors are not reported. + *

+ * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function get enableErrorChecking():Boolean { return _enableErrorChecking; } + /** @private */ + public function set enableErrorChecking( value:Boolean ):void { _enableErrorChecking = value; } + + /** + * Specifies whether we use the endpoint /debug/collect + * instead of /collect. + * + *

+ * The "debug mode", if set to true, allows to send the + * hit requests to the Measurement Protocol Validation Server. + *

+ * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function get debug():Boolean { return _debug; } + /** @private */ + public function set debug( value:Boolean ):void { _debug = value; } + + /** + * Specifies whether the output will be verbose or not. + * + *

+ * The "verbose mode", if set to true, will print the details + * of the hit request to the standard output. + *

+ * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function get verbose():Boolean { return _verbose; } + /** @private */ + public function set verbose( value:Boolean ):void { _verbose = value; } + + /** + * Open a connection to a host on a specific port. + * + *

+ * Will call the onConnect() function if defined. + *

+ * + * @param host The remote host to connect to. + * @param port The port to connect to (optional, default to 80). + * + * Errors + * "CEAIrror: EAI_NONAME #8: nodename nor servname provided, or not known" + * the host provided was not found + * + * "CError: ETIMEDOUT #60: Operation timed out" + * most likely the port provided is not opne on this particular host + * (for ex: try to connect on www.comain.com on port 8080 when only port 80 is open) + * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected function open( host:String, port:int = -1 ):void + { + //trace( "HttpConnection.open()" ); + + if( port == -1 ) + { + port = DEFAULT_PORT; + } + + var sockfd:int = _open( host, String( port ) ); + + if( sockfd == -1 ) + { + //trace( "Could not connect" ); + if( enableErrorChecking ) + { + throw new Error( "Could not connect to " + host + ":" + port ); + } + } + else + { + _sockfd = sockfd; + _connected = true; + _findLocalAddressAndPort(); + + if( onConnect ) { this.onConnect(); } + } + } + + /** + * Close the connection. + * + *

+ * Will call the onDisconnect() function if defined. + *

+ * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected function close():void + { + //trace( "HttpConnection.close()" ); + + if( !_connected ) + { + /* NOTE + never connected + or already disconnected + */ + return; + } + + var result:int = C.unistd.close( _sockfd ); + if( result == -1 ) + { + var e:CError = new CError( "", errno ); + if( enableErrorChecking ) { throw e; } + } + + if( onDisconnect ) { this.onDisconnect(); } + _reset(); + } + + /** + * Send bytes on the socket connection. + * + * @param bytes the bytes to send. + * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected function sendBytes( bytes:ByteArray ):void + { + var sent:int = sendall( _sockfd, bytes ); + //trace( "sent " + sent + " bytes" ); + if( sent < 0 ) + { + var e:CError = new CError( "", errno ); + if( enableErrorChecking ) { throw e; } + } + + if( onSend ) { this.onSend( bytes, sent ); } + } + + /** + * Receive bytes on the socket connection. + * + *

+ * Will call the onReceive() function if defined. + *

+ * + * @return the bytes received. + * + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected function receive():ByteArray + { + var bytes:ByteArray = new ByteArray(); + var received:int = _receiveAll( _sockfd, bytes ); + if( received < 0 ) + { + var e:CError = new CError( "", errno ); + if( enableErrorChecking ) { throw e; } + } + + if( onReceive ) { this.onReceive( bytes, received ); } + return bytes; + } + + + /** @inheritDoc */ + public override function send( model:HitModel ):void + { + var payload:String = _buildHit( model ); + var url:String = ""; + + /* Note: + It is more performant to send a GET request + so by default we want to send a GET request + */ + var sendViaPOST:Boolean = false; + + /* Note: + unless we forcePOST + or the payload size is bigger than maxGETlength (2000 bytes) + then we send a POST request + */ + if( _tracker.config.forcePOST || + (payload.length > _tracker.config.maxGETlength) ) + { + sendViaPOST = true; + } + + /* Note: + if payload is bigger than maxPOSTlength (8192 bytes) + Google Analytics backend will ignore the request + */ + if( payload.length > _tracker.config.maxPOSTlength ) + { + throw new ArgumentError( "POST data is bigger than " + _tracker.config.maxPOSTlength + " bytes." ); + } + + if( _tracker.config.forceSSL || debug ) + { + throw new Error( "HTTPS connection is not supported yet." ); + } + + var ua:String = generateCLIUserAgent(); + var host:String = ""; + var port:int = -1; + var path:String = ""; + var secure:Boolean = false; + + if( _tracker.config.forceSSL ) + { + // https://ssl.google-analytics.com/collect + secure = true; + url = _tracker.config.secureEndpoint; + host = "ssl.google-analytics.com"; + port = SECURE_PORT; + path = "/collect"; + } + else + { + // http://www.google-analytics.com/collect + secure = false; + url = _tracker.config.endpoint; + host = "www.google-analytics.com"; + port = DEFAULT_PORT; + path = "/collect"; + } + + if( debug ) + { + secure = true; + host = "www.google-analytics.com"; + path = "/debug/collect"; + } + + // we build the request + var request:String = ""; + + if( sendViaPOST ) + { + request += "POST " + path + " HTTP/1.1" + _CRLF; + + if( secure ) + { + request += "Host: " + host + ":" + port + _CRLF; + } + else + { + request += "Host: " + host + _CRLF; + } + + request += "User-Agent: " + ua + _CRLF; + request += "Content-Type: " + "application/x-www-form-urlencoded" + _CRLF; + request += "Content-Length: " + (payload.length) + _CRLF; + request += "Connection: close" + _CRLF; + request += _CRLF; + request += payload + _CRLF; + } + else + { + request += "GET " + path + "?" + payload + " HTTP/1.1" + _CRLF; + + if( secure ) + { + request += "Host: " + host + ":" + port + _CRLF; + } + else + { + request += "Host: " + host + _CRLF; + } + + request += "User-Agent: " + ua + _CRLF; + request += "Connection: close" + _CRLF; + request += _CRLF; + } + + if( verbose ) + { + trace( "request:" ); + trace( "--------" ); + trace( request ); + trace( "--------" ); + trace( "" ); + } + + var bytes:ByteArray = new ByteArray(); + bytes.writeUTFBytes( request ); + bytes.position = 0; + + var err:Error = null; + + try + { + open( host, port ); + sendBytes( bytes ); + } + catch( e:Error ) + { + err = e; + } + + if( err ) + { + throw err; + } + + var received:ByteArray = receive(); + received.position = 0; + //trace( "received: " + received.length + " bytes" ); + close(); + + if( verbose ) + { + var response:String = received.readUTFBytes( received.length ); + trace( "response:" ); + trace( "--------" ); + trace( response ); + trace( "--------" ); + trace( "" ); + } + } + + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/senders/CurlHitSender.as b/src/libraries/uanalytics/tracker/senders/CurlHitSender.as new file mode 100644 index 0000000..b934fb5 --- /dev/null +++ b/src/libraries/uanalytics/tracker/senders/CurlHitSender.as @@ -0,0 +1,183 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker.senders +{ + import libraries.uanalytics.tracking.AnalyticsTracker; + import libraries.uanalytics.tracking.Configuration; + import libraries.uanalytics.tracking.HitModel; + import libraries.uanalytics.tracking.HitSender; + + import shell.Program; + import C.unistd.which; + + /** + * A HitSender based on the cURL command-line tool. + * + *

+ * As Redtamarin doesn't have SSL support for sockets on the command-line + * cURL allow to fill this void.
+ * Mainly to be able to test with the Measurement Protocol Validation Server + * which works only with HTTPS, and/or if you absolutely have to use HTTPS. + *

+ * + * @playerversion AVM 0.4 + * @playerversion POSIX + + * @langversion 3.0 + * + * @see http://curl.haxx.se/ cURL + */ + public class CurlHitSender extends HitSender + { + + protected var _tracker:AnalyticsTracker; + + private var _debug:Boolean; + private var _verbose:Boolean; + + public function CurlHitSender( tracker:AnalyticsTracker ) + { + super(); + _tracker = tracker; + + _debug = false; + _verbose = false; + } + + public function get debug():Boolean { return _debug; } + public function set debug( value:Boolean ):void { _debug = value; } + + public function get verbose():Boolean { return _verbose; } + public function set verbose( value:Boolean ):void { _verbose = value; } + + /** @inheritDoc */ + public override function send( model:HitModel ):void + { + var payload:String = _buildHit( model ); + var url:String = ""; + + /* Note: + It is more performant to send a GET request + so by default we want to send a GET request + */ + var sendViaPOST:Boolean = false; + + /* Note: + unless we forcePOST + or the payload size is bigger than maxGETlength (2000 bytes) + then we send a POST request + */ + if( _tracker.config.forcePOST || + (payload.length > _tracker.config.maxGETlength) ) + { + sendViaPOST = true; + } + + /* Note: + if payload is bigger than maxPOSTlength (8192 bytes) + Google Analytics backend will ignore the request + */ + if( payload.length > _tracker.config.maxPOSTlength ) + { + throw new ArgumentError( "POST data is bigger than " + _tracker.config.maxPOSTlength + " bytes." ); + } + + /* Note: + we detect is curl is found in the system paths + for ex: + Mac OS X found it in "/opt/local/bin/curl" + Linux Ubuntu found it in "/usr/bin/curl" + */ + if( which( "curl" ) == "" ) + { + throw new Error( "curl was not found." ); + } + + + var host:String = ""; + var path:String = ""; + var secure:Boolean = false; + + if( _tracker.config.forceSSL ) + { + // https://ssl.google-analytics.com/collect + secure = true; + url = _tracker.config.secureEndpoint; + host = "ssl.google-analytics.com"; + path = "/collect"; + } + else + { + // http://www.google-analytics.com/collect + secure = false; + url = _tracker.config.endpoint; + host = "www.google-analytics.com"; + path = "/collect"; + } + + if( debug ) + { + secure = true; + host = "www.google-analytics.com"; + path = "/debug/collect"; + } + + var curl:String = "curl "; + var curlopt:Array = []; + curlopt.push( "-s" ); + + + if( sendViaPOST ) + { + curlopt.push( "-X POST" ); + curlopt.push( "-d \'" + payload + "\'" ); + url = (secure ? "https://": "http://") + host + path; + } + else + { + curlopt.push( "-X GET" ); + url = (secure ? "https://": "http://") + host + path + "?" + payload; + } + + curl += curlopt.join( " " ); + curl += " \'" + url + "\'"; + + if( verbose ) + { + trace( "command-line:" ); + trace( "--------" ); + trace( curl ); + trace( "--------" ); + trace( "" ); + } + + var response:String = ""; + var err:Error = null; + + try + { + response = Program.open( curl ); + } + catch( e:Error ) + { + err = e; + } + + if( err ) + { + throw err; + } + + if( verbose ) + { + trace( "response:" ); + trace( "--------" ); + trace( response ); + trace( "--------" ); + trace( "" ); + } + } + + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/senders/DebugHitSender.as b/src/libraries/uanalytics/tracker/senders/DebugHitSender.as new file mode 100644 index 0000000..a6a5437 --- /dev/null +++ b/src/libraries/uanalytics/tracker/senders/DebugHitSender.as @@ -0,0 +1,160 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker.senders +{ + import flash.events.Event; + import flash.events.HTTPStatusEvent; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.net.URLRequestMethod; + + import libraries.uanalytics.tracking.AnalyticsTracker; + import libraries.uanalytics.tracking.Configuration; + import libraries.uanalytics.tracking.HitModel; + import libraries.uanalytics.tracking.HitSender; + + /** + * The DebugHitSender will send hit requests + * to the Measurement Protocol Validation Server. + * + *

+ * To ensure that your hits are correctly formatted and contain all required + * parameters, you can test them against the validation server before + * deploying them to production. + *

+ * + *

+ * The sender use the endpoint /debug/collect + * instead of /collect. + * Hits sent to the Measurement Protocol Validation Server will not show up + * in reports. They are for debugging only. + *

+ * + * @see http://developers.google.com/analytics/devguides/collection/protocol/v1/validating-hits Validating Hits + */ + public class DebugHitSender extends URLLoaderHitSender + { + + public function DebugHitSender( tracker:AnalyticsTracker ) + { + super( tracker ); + + /* Note: + the Measurement Protocol Validation Server + returns data in JSON format, so we want TEXT + */ + _loader.dataFormat = URLLoaderDataFormat.TEXT; + } + + protected override function onComplete( event:Event ):void + { + trace( "onLoaderComplete()" ); + _unhookEvents(); + + var raw:String = String( _loader.data ); + + trace( "Measurement Protocol Validation Server:" ); + trace( "--------" ); + //trace( raw ); + //trace( "--------" ); + var obj:Object = JSON.parse( raw ); + var json:String = JSON.stringify( obj, null, " " ); + trace( json ); + + var payload:String = obj.hitParsingResult[0].hit.split( "/debug/collect?" ).join( "" ); + var data:Array = payload.split( "&" ); + var i:uint; + var pair:Array; + for( i = 0; i < data.length; i ++ ) + { + pair = data[i].split( "=" ); + trace( " " + pair[0] + " = " + decodeURIComponent( pair[1] ) ); + } + trace( "--------" ); + } + + /** @inheritDoc */ + public override function send( model:HitModel ):void + { + var payload:String = _buildHit( model ); + var url:String = ""; + + var sendViaPOST:Boolean = false; + + if( _tracker.config.forcePOST || + (payload.length > _tracker.config.maxGETlength) ) + { + sendViaPOST = true; + } + + if( payload.length > _tracker.config.maxPOSTlength ) + { + throw new ArgumentError( "POST data is bigger than " + _tracker.config.maxPOSTlength + " bytes." ); + } + + if( _tracker.config.forceSSL ) + { + url = _tracker.config.secureEndpoint.replace( "/collect", "/debug/collect" ); + } + else + { + url = _tracker.config.endpoint.replace( "/collect", "/debug/collect" ); + } + + var request:URLRequest = new URLRequest(); + request.url = url; + + if( sendViaPOST ) + { + request.method = URLRequestMethod.POST; + } + else + { + request.method = URLRequestMethod.GET; + } + + request.data = payload; + + trace( "payload:" ); + trace( "--------" ); + trace( payload ); + var data:Array = payload.split( "&" ); + var i:uint; + var pair:Array; + for( i = 0; i < data.length; i ++ ) + { + pair = data[i].split( "=" ); + trace( " " + pair[0] + " = " + decodeURIComponent( pair[1] ) ); + } + trace( "--------" ); + + _hookEvents(); + var err:* = null; + + try + { + _loader.load( request ); + } + catch( e:Error ) + { + _unhookEvents(); + trace( "unable to load requested page." ); + trace( "Error: " + e.message ); + err = e; + } + + if( err ) + { + throw err; + } + + } + + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/senders/LoaderHitSender.as b/src/libraries/uanalytics/tracker/senders/LoaderHitSender.as new file mode 100644 index 0000000..1d823c2 --- /dev/null +++ b/src/libraries/uanalytics/tracker/senders/LoaderHitSender.as @@ -0,0 +1,415 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker.senders +{ + import flash.display.Loader; + import flash.errors.IOError; + import flash.errors.IllegalOperationError; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.HTTPStatusEvent; + import flash.events.IOErrorEvent; + import flash.events.UncaughtErrorEvent; + import flash.net.URLRequest; + import flash.net.URLRequestMethod; + + import libraries.uanalytics.tracking.AnalyticsTracker; + import libraries.uanalytics.tracking.HitModel; + import libraries.uanalytics.tracking.HitSender; + + /** + * A simple implementation that will send hit data to Google Analytics servers. + * + *

+ * This is the default implementation we use in all the Flash and AIR trackers + * for the following reasons: + *

+ * + * + *

+ * The only problem you can encounter is with local SWF files outside the + * "User Flash Player Trust directories". + * By default a local SWF file is placed in the local-with-filesystem sandbox + * see security sandboxes + * and will prevent the tracking to work (as it can not access the network). + * Although, this behaviour can be changed by setting the document’s publish + * settings in the authoring tool. + *

+ * + * + *

+ * Different and More complicated implementations could do more things; + * for example: queue hits and send them at a later time, validate the hit + * by checking the HTTP response code, etc. + *

+ * + *

+ * Note: + *

+ *
+     * When you use the Loader class, consider the Flash Player
+     * and Adobe AIR security model:
+     * 
+     *   - You can load content from any accessible source.
+     * 
+     *   - Loading is not allowed if the calling SWF file is in a network sandbox
+     *     and the file to be loaded is local.
+     * 
+     *   - If the loaded content is a SWF file written with ActionScript 3.0,
+     *     it cannot be cross-scripted by a SWF file in another security sandbox
+     *     unless that cross-scripting arrangement was approved through a call
+     *     to the System.allowDomain() or the System.allowInsecureDomain() method
+     *     in the loaded content file.
+     * 
+     *   - If the loaded content is an AVM1 SWF file
+     *     (written using ActionScript 1.0 or 2.0), it cannot be cross-scripted
+     *     by an AVM2 SWF file (written using ActionScript 3.0). However, you
+     *     can communicate between the two SWF files by using the LocalConnection class.
+     * 
+     *   - If the loaded content is an image, its data cannot be accessed by a
+     *     SWF file outside of the security sandbox, unless the domain of that
+     *     SWF file was included in a URL policy file at the origin domain of
+     *     the image.
+     * 
+     *   - Movie clips in the local-with-file-system sandbox cannot script movie
+     *     clips in the local-with-networking sandbox, and the reverse is also
+     *     prevented.
+     * 
+     *   - You cannot connect to commonly reserved ports. For a complete list of
+     *     blocked ports, see “Restricting Networking APIs” in the ActionScript
+     *     3.0 Developer’s Guide.
+     * 
+     * However, in AIR, content in the application security sandbox
+     * (content installed with the AIR application) are not restricted by these
+     * security limitations.
+     * 
+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/Loader.html Loader + * @see http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7e3f.html Security sandboxes + * @see http://help.adobe.com/en_US/as3/dev/WS1EFE2EDA-026D-4d14-864E-79DFD56F87C6.html Restricting networking APIs + */ + public class LoaderHitSender extends HitSender + { + + /** + * An Analytics tracker. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected var _tracker:AnalyticsTracker; + + /** + * A Loader. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected var _loader:Loader; + + /** + * Creates a LoaderHitSender. + * + * @param tracker an analytics tracker + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + public function LoaderHitSender( tracker:AnalyticsTracker ) + { + super(); + _tracker = tracker; + _loader = new Loader(); + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function _hookEvents():void + { + _loader.uncaughtErrorEvents.addEventListener( UncaughtErrorEvent.UNCAUGHT_ERROR, onUncaughtError ); + + _loader.contentLoaderInfo.addEventListener( HTTPStatusEvent.HTTP_STATUS, onHTTPStatus ); + _loader.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, onIOError ); + _loader.contentLoaderInfo.addEventListener( Event.COMPLETE, onComplete ); + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function _unhookEvents():void + { + _loader.uncaughtErrorEvents.removeEventListener( UncaughtErrorEvent.UNCAUGHT_ERROR, onUncaughtError ); + + _loader.contentLoaderInfo.removeEventListener( HTTPStatusEvent.HTTP_STATUS, onHTTPStatus ); + _loader.contentLoaderInfo.removeEventListener( IOErrorEvent.IO_ERROR, onIOError ); + _loader.contentLoaderInfo.removeEventListener( Event.COMPLETE, onComplete ); + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function onUncaughtError( event:UncaughtErrorEvent ):void + { + /* Note: + An error occured and so we want to unhook all our events + */ + _unhookEvents(); + + var error:Error; + + if( event.error is Error ) + { + error = event.error as Error; + } + else if( event.error is ErrorEvent ) + { + var errorEvent:ErrorEvent = event.error as ErrorEvent; + error = new Error( errorEvent.text, errorEvent.errorID ); + } + else + { + error = new Error( "a non-Error, non-ErrorEvent type was thrown and uncaught" ); + } + + if( _tracker.config.enableErrorChecking ) + { + throw error; + } + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function onHTTPStatus( event:HTTPStatusEvent ):void + { + /* Note: + An advanced implementation could detect the HTTP status code + and if not equal to 200 block any following hit requests. + + see: + Response Codes https://developers.google.com/analytics/devguides/collection/protocol/v1/reference#response-codes + + The Measurement Protocol will return a 2xx status code if the + HTTP request was received. + The Measurement Protocol does not return an error code if the + payload data was malformed, or if the data in the payload was + incorrect or was not processed by Google Analytics. + + If you do not get a 2xx status code, you should NOT retry the request. + Instead, you should stop and correct any errors in your HTTP request. + */ + /* + if( event.status == 200 ) + { + // do something here + } + */ + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function onIOError( event:IOErrorEvent ):void + { + /* Note: + An error occured and so we want to unhook all our events + */ + _unhookEvents(); + + if( _tracker.config.enableErrorChecking ) + { + var error:IOError = new IOError( event.text, event.errorID ); + throw error; + } + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function onComplete( event:Event ):void + { + /* Note: + We are done and so we want to unhook all our events + */ + _unhookEvents(); + } + + /** @inheritDoc */ + public override function send( model:HitModel ):void + { + var payload:String = _buildHit( model ); + var url:String = ""; + + /* Note: + It is more performant to send a GET request + so by default we want to send a GET request + */ + var sendViaPOST:Boolean = false; + + /* Note: + unless we forcePOST + or the payload size is bigger than maxGETlength (2000 bytes) + then we send a POST request + */ + if( _tracker.config.forcePOST || + (payload.length > _tracker.config.maxGETlength) ) + { + sendViaPOST = true; + } + + /* Note: + if payload is bigger than maxPOSTlength (8192 bytes) + Google Analytics backend will ignore the request + */ + if( payload.length > _tracker.config.maxPOSTlength ) + { + throw new ArgumentError( "POST data is bigger than " + _tracker.config.maxPOSTlength + " bytes." ); + } + + if( _tracker.config.forceSSL ) + { + url = _tracker.config.secureEndpoint; + } + else + { + url = _tracker.config.endpoint; + } + + // we build the request + var request:URLRequest = new URLRequest(); + request.url = url; + + if( sendViaPOST ) + { + request.method = URLRequestMethod.POST; + } + else + { + request.method = URLRequestMethod.GET; + } + + request.data = payload; + + _hookEvents(); + var err:* = null; + + /* Note: + Here we wrap the sender call in a try/catch + only to remove our event listeners if an error occur + but we do want to throw errors. + + this is not the same as config.enableErrorChecking + in a tracker. + */ + try + { + // we send the request + _loader.load( request ); + } + catch( e:IOError ) + { + _unhookEvents(); + //trace( "unable to load requested page." ); + //trace( "IOError: " + e.message ); + err = e; + } + catch( e:SecurityError ) + { + _unhookEvents(); + //trace( "unable to load requested page." ); + //trace( "SecurityError: " + e.message ); + err = e; + } + catch( e:IllegalOperationError ) + { + _unhookEvents(); + //trace( "unable to load requested page." ); + //trace( "IllegalOperationError: " + e.message ); + err = e; + } + catch( e:Error ) + { + _unhookEvents(); + //trace( "unable to load requested page." ); + //trace( "Error: " + e.message ); + err = e; + } + + // a HitSender always throw errors + if( err ) + { + throw err; + } + + } + + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/senders/TraceHitSender.as b/src/libraries/uanalytics/tracker/senders/TraceHitSender.as new file mode 100644 index 0000000..939a158 --- /dev/null +++ b/src/libraries/uanalytics/tracker/senders/TraceHitSender.as @@ -0,0 +1,158 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker.senders +{ + import libraries.uanalytics.tracking.AnalyticsTracker; + import libraries.uanalytics.tracking.Configuration; + import libraries.uanalytics.tracking.HitModel; + import libraries.uanalytics.tracking.HitSender; + + /** + * A HitSender that will trace the hit requests to the console + * output. + * + *

+ * It will work only in a debug Flash Player, debug AIR application, + * and with the debug redshell (or as3shebang). + * It will not send any data to the Google Analytics servers. + *

+ * + * @example Usage + * + * var config:Configuration = new Configuration(); + * // replace the sender type + * config.senderType = "libraries.uanalytics.tracker.senders.TraceHitSender"; + * + * var tracker:WebTracker = new WebTracker( "UA-12345-67", config ); + * tracker.pageview( "/hello/world", "Hello World" ); + * + * //output: + * /* + * request: + * -------- + * GET /collect?v=1&_v=as3uanalytics1&cid=1898e890-5aad-49a8-b7f2-83751bab4c9c&dh=localhost&dp=%2Fhello%2Fworld&ds=web&dt=Hello%20World&t=pageview&tid=UA-12345-67 HTTP/1.1 + * Host: http://www.google-analytics.com + * User-Agent: user_agent_string + * + * + * v = 1 + * _v = as3uanalytics1 + * cid = 1898e890-5aad-49a8-b7f2-83751bab4c9c + * dh = localhost + * dp = /hello/world + * ds = web + * dt = Hello World + * t = pageview + * tid = UA-12345-67 + * + * -------- + * */ + * + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public class TraceHitSender extends HitSender + { + private var _tracker:AnalyticsTracker; + + /** + * Creates a TraceHitSender. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function TraceHitSender( tracker:AnalyticsTracker ) + { + super(); + _tracker = tracker; + } + + /** @inheritDoc */ + public override function send( model:HitModel ):void + { + var payload:String = _buildHit( model ); + var url:String = ""; + + var sendViaPOST:Boolean = false; + + if( _tracker.config.forcePOST || + (payload.length > _tracker.config.maxGETlength) ) + { + sendViaPOST = true; + } + + if( payload.length > _tracker.config.maxPOSTlength ) + { + throw new ArgumentError( "POST data is bigger than " + _tracker.config.maxPOSTlength + " bytes." ); + } + + if( _tracker.config.forceSSL ) + { + url = _tracker.config.secureEndpoint; + } + else + { + url = _tracker.config.endpoint; + } + + var str:String = ""; + + if( sendViaPOST ) + { + /* POST: + ---- + User-Agent: user_agent_string + POST http://www.google-analytics.com/collect + payload_data + ---- + */ + str += "User-Agent: user_agent_string\n"; + str += "POST " + url + "\n"; + str += payload; + str += "\n\n"; + } + else + { + // that's quick poorman parsing but it works ;) + // http://www.google-analytics.com/collect + // https://ssl.google-analytics.com/collect + var domain:String = url.split( "/collect" )[0]; + var path:String = "/collect"; + + /* GET: + ---- + GET /collect?payload_data HTTP/1.1 + Host: http://www.google-analytics.com + User-Agent: user_agent_string + ---- + */ + str += "GET " + path + "?" + payload + " HTTP/1.1\n"; + str += "Host: " + domain + "\n"; + str += "User-Agent: user_agent_string\n"; + str += "\n\n"; + } + + var data:Array = payload.split( "&" ); + var i:uint; + var pair:Array; + for( i = 0; i < data.length; i ++ ) + { + pair = data[i].split( "=" ); + str += " " + pair[0] + " = " + decodeURIComponent( pair[1] ) + "\n"; + } + + trace( "request:" ); + trace( "--------" ); + trace( str ); + trace( "--------" ); + throw new Error( "hit not sent" ); + } + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/senders/URLLoaderHitSender.as b/src/libraries/uanalytics/tracker/senders/URLLoaderHitSender.as new file mode 100644 index 0000000..a6ba81b --- /dev/null +++ b/src/libraries/uanalytics/tracker/senders/URLLoaderHitSender.as @@ -0,0 +1,259 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker.senders +{ + import flash.events.Event; + import flash.events.HTTPStatusEvent; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.net.URLRequestMethod; + + import libraries.uanalytics.tracking.AnalyticsTracker; + import libraries.uanalytics.tracking.HitModel; + import libraries.uanalytics.tracking.HitSender; + + /** + * A hitSender implemented with URLLoader. + * + *

+ * Note: + *

+ *
+     * When you use this class in Flash Player and in AIR application content
+     * in security sandboxes other than then application security sandbox,
+     * consider the following security model:
+     * 
+     *   - A SWF file in the local-with-filesystem sandbox may not load data
+     *     from, or provide data to, a resource that is in the network sandbox.
+     * 
+     *   - By default, the calling SWF file and the URL you load must be in
+     *     exactly the same domain. For example, a SWF file at www.adobe.com
+     *     can load data only from sources that are also at www.adobe.com.
+     *     To load data from a different domain, place a URL policy file on the
+     *     server hosting the data.
+     * 
+     * 
+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/URLLoader.html URLLoader + */ + public class URLLoaderHitSender extends HitSender + { + + /** + * An Analytics tracker. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected var _tracker:AnalyticsTracker; + + /** + * A URLLoader. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected var _loader:URLLoader; + + /** + * Creates a URLLoaderHitSender. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + public function URLLoaderHitSender( tracker:AnalyticsTracker ) + { + super(); + _tracker = tracker; + _loader = new URLLoader(); + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function _hookEvents():void + { + _loader.addEventListener( Event.OPEN, onOpen ); + _loader.addEventListener( ProgressEvent.PROGRESS , onProgress ); + _loader.addEventListener( HTTPStatusEvent.HTTP_STATUS, onHTTPStatus ); + _loader.addEventListener( SecurityErrorEvent.SECURITY_ERROR, onSecurityError ); + _loader.addEventListener( IOErrorEvent.IO_ERROR, onIOError ); + _loader.addEventListener( Event.COMPLETE, onComplete ); + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function _unhookEvents():void + { + _loader.removeEventListener( Event.OPEN, onOpen ); + _loader.removeEventListener( ProgressEvent.PROGRESS , onProgress ); + _loader.removeEventListener( HTTPStatusEvent.HTTP_STATUS, onHTTPStatus ); + _loader.removeEventListener( SecurityErrorEvent.SECURITY_ERROR, onSecurityError ); + _loader.removeEventListener( IOErrorEvent.IO_ERROR, onIOError ); + _loader.removeEventListener( Event.COMPLETE, onComplete ); + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function onOpen( event:Event ):void + { + //nothing + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function onProgress( event:ProgressEvent ):void + { + //nothing + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function onHTTPStatus( event:HTTPStatusEvent ):void + { + /* Note: + Here you can deal with the HTTP response status + see comments in LoaderHitSender.onHTTPStatus() + */ + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function onSecurityError( event:SecurityErrorEvent ):void + { + /* Note: + An error occured and so we want to unhook all our events + */ + _unhookEvents(); + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function onIOError( event:IOErrorEvent ):void + { + /* Note: + An error occured and so we want to unhook all our events + */ + _unhookEvents(); + } + + /** + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected function onComplete( event:Event ):void + { + /* Note: + We are done and so we want to unhook all our events + */ + _unhookEvents(); + } + + /** @inheritDoc */ + public override function send( model:HitModel ):void + { + var payload:String = _buildHit( model ); + var url:String = ""; + + var sendViaPOST:Boolean = false; + + if( _tracker.config.forcePOST || + (payload.length > _tracker.config.maxGETlength) ) + { + sendViaPOST = true; + } + + if( payload.length > _tracker.config.maxPOSTlength ) + { + throw new ArgumentError( "POST data is bigger than " + _tracker.config.maxPOSTlength + " bytes." ); + } + + if( _tracker.config.forceSSL ) + { + url = _tracker.config.secureEndpoint; + } + else + { + url = _tracker.config.endpoint; + } + + var request:URLRequest = new URLRequest(); + request.url = url; + + if( sendViaPOST ) + { + request.method = URLRequestMethod.POST; + } + else + { + request.method = URLRequestMethod.GET; + } + + request.data = payload; + + _hookEvents(); + var err:* = null; + + try + { + _loader.load( request ); + } + catch( e:Error ) + { + _unhookEvents(); + //trace( "unable to load requested page." ); + //trace( "Error: " + e.message ); + err = e; + } + + if( err ) + { + throw err; + } + + } + + + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracker/senders/URLStreamHitSender.as b/src/libraries/uanalytics/tracker/senders/URLStreamHitSender.as new file mode 100644 index 0000000..a238ad0 --- /dev/null +++ b/src/libraries/uanalytics/tracker/senders/URLStreamHitSender.as @@ -0,0 +1,232 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracker.senders +{ + import flash.errors.MemoryError; + import flash.events.Event; + import flash.events.HTTPStatusEvent; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.net.URLRequest; + import flash.net.URLRequestMethod; + import flash.net.URLStream; + + import libraries.uanalytics.tracking.AnalyticsTracker; + import libraries.uanalytics.tracking.HitModel; + import libraries.uanalytics.tracking.HitSender; + + /** + * A hitSender implemented with URLStream. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/URLStream.html URLStream + */ + public class URLStreamHitSender extends HitSender + { + + /** + * An Analytics tracker. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected var _tracker:AnalyticsTracker; + + /** + * A URLStream. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + protected var _loader:URLStream; + + /** + * Creates a URLStreamHitSender. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + public function URLStreamHitSender( tracker:AnalyticsTracker ) + { + super(); + _tracker = tracker; + _loader = new URLStream(); + } + + protected function _hookEvents():void + { + _loader.addEventListener( Event.OPEN, onOpen ); + _loader.addEventListener( ProgressEvent.PROGRESS, onProgress ); + _loader.addEventListener( HTTPStatusEvent.HTTP_STATUS, onHTTPStatus ); + //_loader.addEventListener( HTTPStatusEvent.HTTP_RESPONSE_STATUS, onHTTPResponseStatus ); + _loader.addEventListener( SecurityErrorEvent.SECURITY_ERROR, onSecurityError ); + _loader.addEventListener( IOErrorEvent.IO_ERROR, onIOError ); + _loader.addEventListener( Event.COMPLETE, onComplete ); + } + + protected function _unhookEvents():void + { + _loader.removeEventListener( Event.OPEN, onOpen ); + _loader.removeEventListener( ProgressEvent.PROGRESS, onProgress ); + _loader.removeEventListener( HTTPStatusEvent.HTTP_STATUS, onHTTPStatus ); + //_loader.removeEventListener( HTTPStatusEvent.HTTP_RESPONSE_STATUS, onHTTPResponseStatus ); + _loader.removeEventListener( SecurityErrorEvent.SECURITY_ERROR, onSecurityError ); + _loader.removeEventListener( IOErrorEvent.IO_ERROR, onIOError ); + _loader.removeEventListener( Event.COMPLETE, onComplete ); + } + + protected function onOpen( event:Event ):void + { + //trace( "onOpen()" ); + } + + protected function onProgress( event:ProgressEvent ):void + { + //trace( "onProgress()" ); + } + + protected function onHTTPStatus( event:HTTPStatusEvent ):void + { + //trace( "onHTTPStatus()" ); + } + + protected function onHTTPResponseStatus( event:HTTPStatusEvent ):void + { + //trace( "onHTTPResponseStatus()" ); + } + + protected function onSecurityError( event:SecurityErrorEvent ):void + { + /* Note: + An error occured and so we want to unhook all our events + */ + _unhookEvents(); + //trace( "onSecurityError()" ); + } + + protected function onIOError( event:IOErrorEvent ):void + { + /* Note: + An error occured and so we want to unhook all our events + */ + _unhookEvents(); + //trace( "onIOError()" ); + } + + protected function onComplete( event:Event ):void + { + /* Note: + We are done and so we want to unhook all our events + */ + _unhookEvents(); + //trace( "onComplete()" ); + } + + /** @inheritDoc */ + public override function send( model:HitModel ):void + { + var payload:String = _buildHit( model ); + var url:String = ""; + + var sendViaPOST:Boolean = false; + + if( _tracker.config.forcePOST || + (payload.length > _tracker.config.maxGETlength) ) + { + sendViaPOST = true; + } + + if( payload.length > _tracker.config.maxPOSTlength ) + { + throw new ArgumentError( "POST data is bigger than " + _tracker.config.maxPOSTlength + " bytes." ); + } + + if( _tracker.config.forceSSL ) + { + url = _tracker.config.secureEndpoint; + } + else + { + url = _tracker.config.endpoint; + } + + var request:URLRequest = new URLRequest(); + request.url = url; + + if( sendViaPOST ) + { + request.method = URLRequestMethod.POST; + } + else + { + request.method = URLRequestMethod.GET; + } + + request.data = payload; + + _hookEvents(); + var err:* = null; + + try + { + _loader.load( request ); + } + catch( e:ArgumentError ) + { + _unhookEvents(); + //trace( "objects may not contain certain prohibited HTTP request headers." ); + //trace( "ArgumentError: " + e.message ); + err = e; + } + catch( e:MemoryError ) + { + _unhookEvents(); + /* + if( request.method == URLRequestMethod.GET ) + { + trace( "cannot convert the URLRequest.data parameter from UTF8 to MBCS." ); + } + else if( request.method == URLRequestMethod.POST ) + { + trace( "cannot allocate memory for the POST data." ); + } + */ + + //trace( "MemoryError: " + e.message ); + err = e; + } + catch( e:SecurityError ) + { + _unhookEvents(); + //trace( "Local untrusted SWF files may not communicate with the Internet." ); + //trace( "OR" ); + //trace( "You are trying to connect to a commonly reserved port." ); + //trace( "Error: " + e.message ); + err = e; + } + catch( e:Error ) + { + _unhookEvents(); + //trace( "unable to load requested page." ); + //trace( "Error: " + e.message ); + err = e; + } + + if( err ) + { + throw err; + } + + } + + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracking/AnalyticsSender.as b/src/libraries/uanalytics/tracking/AnalyticsSender.as new file mode 100644 index 0000000..fa05217 --- /dev/null +++ b/src/libraries/uanalytics/tracking/AnalyticsSender.as @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracking +{ + + /** + * The contract to send a hit request to Google Analytics servers. + * + *

+ * The strict minimum of what a sender need to implement + * is to send a data model to a URL. + *

+ * + *

+ * For a Flash / AIR implementation see URLLoaderHitSender, + * for a Redtamarin implementation see BSDSocketHitSender. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see HitSender + * @see libraries.uanalytics.tracker.senders.URLLoaderHitSender URLLoaderHitSender + * @see libraries.uanalytics.tracker.senders.BSDSocketHitSender BSDSocketHitSender + */ + public interface AnalyticsSender + { + + /** + * Sends the hit in the specified data model. + * + * @param model the hit to send. + * + * @throws Error if there is a problem sending the data. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + function send( model:HitModel ):void; + + } + +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracking/AnalyticsTracker.as b/src/libraries/uanalytics/tracking/AnalyticsTracker.as new file mode 100644 index 0000000..097ad25 --- /dev/null +++ b/src/libraries/uanalytics/tracking/AnalyticsTracker.as @@ -0,0 +1,436 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracking +{ + import flash.utils.Dictionary; + + /** + * The contract of a Google Analytics tracker. + * + *

+ * The strict minimum of what a tracker need to implement: + *

+ * + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracker.DefaultTracker DefaultTracker + * @see libraries.uanalytics.tracker.WebTracker WebTracker + * @see libraries.uanalytics.tracker.AppTracker AppTracker + * @see libraries.uanalytics.tracker.CliTracker CliTracker + */ + public interface AnalyticsTracker + { + + /** + * The Tracking ID / Web Property ID. + * + *

+ * The format is UA-XXXX-Y. + * All collected data is associated by this ID. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + function get trackingId():String; + + /** + * The Client ID anonymously identifies a particular user, + * device, or browser instance. + * + *

+ * For the web, this is generally stored as a first-party cookie with + * a two-year expiration. + * For mobile apps, this is randomly generated for each particular + * instance of an application install. + * The value of this field should be a random UUID (version 4) + * as described in rfc4122. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + function get clientId():String; + + /** + * The tracking configuration. + * + *

+ * The configuration is applied for each hits, if you change properties + * between hits, it will affect the way hits are processed. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + function get config():Configuration; + + /** + * Send the hit to Google Analytics servers. + * + * @example Usage + * + * + * + * @param hitType the hit type to send, e.g. "pageview" or "event". + * @param tempValues a map of field and value pairs to be sent with + * the hit that override current data model setting. + * @return false if the hit has not been sent due to sampling + * or other reasons. + * + * @throws IOError if there is an error sending the hit. + * @throws RateLimitError if hits are being sent too quickly in succession. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + function send( hitType:String = null, tempValues:Dictionary = null ):Boolean; + + /** + * Send a pageview hit. + * + *

+ * Page tracking allows you to measure the number of views you had for a + * particular page on your website. + *

+ * + *

+ * Pages often correspond to an an entire HTML document, but they can + * also represent dynamically loaded content; + * this is known as "virtual pageviews". + *

+ * + * @example Usage + * + * tracker.pageview( "/hello/world", "Hello World" ); + * + * + * @param path the page / document path + * @param title the page / document title + * + * @return false if the hit has not been sent due to sampling + * or other reasons. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + function pageview( path:String, title:String = "" ):Boolean; + + /** + * Send a screenview hit. + * + *

+ * Screens in Google Analytics represent content users are viewing + * within your app. + * The equivalent concept in web analytics is a pageview. + *

+ * + *

+ * Measuring screen views allows you to see which content is being + * viewed most by your users, and how they are navigating between + * different pieces of content. + *

+ * + * @example Usage + * + * tracker.screenview( "High Scores" ); + * + * + * @param name the screen name + * @param appinfo additional ApplicationInfo (optional) + * + * @return false if the hit has not been sent due to sampling + * or other reasons. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + function screenview( name:String, appinfo:Dictionary = null ):Boolean; + + /** + * Send an event hit. + * + *

+ * Events are user interactions with content that can be tracked + * independently from a web page or a screen load. + *

+ * + *

+ * Downloads, mobile ad clicks, gadgets, Flash elements, AJAX embedded + * elements, and video plays are all examples of actions you might want + * to track as Events. + *

+ * + * @example Usage + * + * tracker.event( "Videos", "play", "Big Buck Bunny" ); + * + * + * @param category the event category. + * Must not be empty. + * @param action the event action. + * Must not be empty. + * @param label the event label (optional). + * @param value the event value (optional). + * Values must be non-negative. + * + * @return false if the hit has not been sent due to sampling + * or other reasons. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://support.google.com/analytics/answer/1033068 About Events + */ + function event( category:String, action:String, + label:String = "", value:int = -1 ):Boolean; + + /** + * Send a transaction hit. + * + *

+ * Ecommerce tracking allows you to measure the number of transactions + * and revenue that your website generates. + *

+ * + *

+ * A transaction represents the entire transaction that occurs on your + * site. + *

+ * + * @example Usage + * + * tracker.transaction( "1234", "Acme Clothing", 11.99, 5, 1.29, "EUR" ); + * + * + * @param id The transaction ID. + * @param affiliation The store or affiliation + * from which this transaction occurred. + * @param revenue Specifies the total revenue or grand total + * associated with the transaction. + * @param shipping Specifies the total shipping cost + * of the transaction. + * @param tax Specifies the total tax of the transaction. + * @param currency When present indicates the local currency for + * all transaction currency values. + * + * @return false if the hit has not been sent due to sampling + * or other reasons. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://support.google.com/analytics/answer/6205902#supported-currencies Currency Codes Reference + */ + function transaction( id:String, + affiliation:String = "", + revenue:Number = 0, + shipping:Number = 0, + tax:Number = 0, + currency:String = "" ):Boolean; + + /** + * Send an item hit. + * + *

+ * An item represents the individual products that were in the shopping + * cart. + *

+ * + * @example Usage + * + * tracker.item( "1234", "Fluffy Pink Bunnies", 11.99, 1, "DD23444", "Party Toys" ); + * + * + * @param transactionId The transaction ID. + * This ID is what links items to the transactions + * to which they belong. + * @param name The item name. + * @param price The individual, unit, price for each item. + * @param quantity The number of units purchased in the transaction. + * @param code Specifies the SKU or item code. + * @param category The category to which the item belongs. + * @param currency + * + * @return false if the hit has not been sent due to sampling + * or other reasons. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://en.wikipedia.org/wiki/Stock_keeping_unit SKU + */ + function item( transactionId:String, name:String, + price:Number = 0, + quantity:int = 0, + code:String = "", + category:String = "", + currency:String = "" ):Boolean; + + /** + * Send a social hit. + * + *

+ * Social interaction measurement allows you to measure a user's + * interactions with various social network sharing and recommendation + * widgets embedded in your content. + *

+ * + *

+ * While event tracking measures general user-interactions very well, + * Social Analytics provides a consistent framework for recording social + * interactions. + * This in turn provides a consistent set of reports to compare social + * network interactions across multiple networks. + *

+ * + * @example Usage + * + * tracker.social( "Facebook", "Like", "Http://as3lang.org" ); + * + * + * @param network The social network with which the user is interacting. + * @param action The social action taken. + * @param target The content on which the social action is being taken. + * + * @return false if the hit has not been sent due to sampling + * or other reasons. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://support.google.com/analytics/answer/6209874 About Social plugins and interactions + */ + function social( network:String, action:String, target:String ):Boolean; + + /** + * Send an exception hit. + * + *

+ * Exception tracking allows you to measure the number and type of + * crashes or errors that occur on your property. + *

+ * + * @example Usage + * + * try + * { + * //an error is thrown here + * } + * catch( e:Error ) + * { + * tracker.exception( e.message, false ); + * } + * + * + * @param description A description of the exception. + * @param isFatal true if the exception was fatal. + * + * @return false if the hit has not been sent due to sampling + * or other reasons. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + function exception( description:String = "", + isFatal:Boolean = true ):Boolean; + + /** + * Send a timing hit. + * + *

+ * Allows to measure periods of time, particularly useful for developers + * to measure the latency, or time spent, executing a particular action. + *

+ * + *

+ * Google Analytics has a number of powerful reports that automatically + * measure and report on page load times. + * However, it is also possible to track custom timing information to + * measure performance specific to your site. + *

+ * + * @example Usage + * + * // in a Flash/AIR app it took 100ms to load a PNG image + * tracker.timing( "timing", "imageloader", 100, "poster.png" ); + * + * + * + * // client-side, it took 300ms to receive the response + * tracker.timing( "client", "REST", 300, "GET /api/version" ); + * + * // server-side, it took 100ms to proces the request + * tracker.timing( "server", "REST", 100, "GET /api/version" ); + * + * + * @param category the user timing category + * for categorizing all user timing variables into + * logical groups + * @param name the user timing variable + * to identify the variable being recorded + * @param value the user timing value, + * the number of milliseconds in elapsed time + * to report to Google Analytics + * @param label the user timing label (optional), + * can be used to add flexibility in visualizing + * user timings in the reports + * @param timinginfo additional TimingInfo (optional) + * + * @return false if the hit has not been sent due to sampling + * or other reasons. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + function timing( category:String, name:String, value:int, + label:String = "", + timinginfo:Dictionary = null ):Boolean; + + + + + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracking/Configuration.as b/src/libraries/uanalytics/tracking/Configuration.as new file mode 100644 index 0000000..2df569c --- /dev/null +++ b/src/libraries/uanalytics/tracking/Configuration.as @@ -0,0 +1,357 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracking +{ + /** + * Analytics Tracking Configuration. + * + *

+ * Those are the default configuration settings used by all the trackers.
+ * It is possible to overide them and provide your own configuration. + *

+ * + *

+ * You can change the configuration settings while in use by the trackers, + * each time a hit request is send the configuration is applied again. + *

+ * + * @example Usage + * + * var config:Configuration = new Configuration(); + * config.forcePOST = true; + * config.forceSSL = true; + * config.anonymizeIp = true; + * + * var tracker:WebTracker = new WebTracker( trackingID, config ); + * + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://github.com/zwetan/as3-universal-analytics/wiki/TrackingConfiguration Tracking Configuration + */ + public class Configuration + { + + /** + * The SDK version number and/or signature. + * + *

+ * Will add the undocumented _v parameters + * to every hitSender requests. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static var SDKversion:String = "as3uanalytics1"; + + /* Note: + Those variables should not be edited. + */ + private var _endpoint:String = "http://www.google-analytics.com/collect"; + private var _secureEndpoint:String = "https://ssl.google-analytics.com/collect"; + private var _maxGETlength:uint = 2000; + private var _maxPOSTlength:uint = 8192; + private var _storageName:String = "_ga"; + + + /* read-write */ + + /** + * Allows to dynamically change the type of HitSender + * used by the tracker. + * + *

+ * If empty, the tracker will use the default HitSender: + * LoaderHitSender for Flash and AIR, + * BSDSocketHitSender for Redtamarin. + *

+ * + *

+ * To override the HitSender you need to provide the full + * class path, for example if oyu want to use TraceHitSender + * use the string "libraries.uanalytics.tracker.senders.TraceHitSender", + * not just "TraceHitSender". + *

+ * + *

+ * Attention:
+ * You will have to define a reference to this HitSender + * in your code, otherwise the code reflection will not be able to find + * it in memory. + *

+ * + * @example usage + * + * //important! + * var tmp:DebugHitSender; + * + * config.senderType = "libraries.uanalytics.tracker.senders.DebugHitSender"; + * + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public var senderType:String = ""; + + /** + * Specifies whether errors encountered by the trackers are reported + * to the application. + * + *

+ * Default to false, we don't want the tracker + * to throw any errors at all. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public var enableErrorChecking:Boolean = false; + + /** + * Specifies whether or not the RateLimiter will ensure + * the tracker does not send too many hits at once. + * + *

+ * Default to true, each trackers define by default the + * capacity, rate and span of the number of hits that can be send. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see RateLimiter + * @see http://github.com/zwetan/as3-universal-analytics/wiki/TrackingDetailsAndTricks#throttling Tracking Details and Tricks: Throttling + */ + public var enableThrottling:Boolean = true; + + /** + * Specifies whether or not a particular hit should be sent to + * Google Analytics collection servers due to sampling. + * + *

+ * Default to true, each tracker will use the sampleRate + * to select a subset (in percentage) of data to send. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see HitSampler + * @see #sampleRate + * @see http://github.com/zwetan/as3-universal-analytics/wiki/TrackingDetailsAndTricks#sampling Tracking Details and Tricks: Sampling + */ + public var enableSampling:Boolean = true; + + /** + * Specifies whether a "cache buster" will be automatically added to the + * hit request to ensure browsers and proxies don't cache hits. + * + *

+ * Default to false, as in general we don't need it. + * Also note that a cache buster will be used only with GET requests, + * not with POST requests. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#z Cache Buster + */ + public var enableCacheBusting:Boolean = false; + + /** + * Setting this property to true will force the HitSender + * to use the HTTPS protocol. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public var forceSSL:Boolean = false; + + /** + * Setting this property to true will force the HitSender + * to use send requests using POST. + * + *

+ * When processinga hit request we do check maxGETlength + * and if the payload data is bigger the tracker will automatically switch + * to POST. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public var forcePOST:Boolean = false; + + /** + * Specifies what percentage of users should be tracked. + * + *

+ * This defaults to 100 (no users are sampled out) but large sites may + * need to use a lower sample rate to stay within Google Analytics + * processing limits. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public var sampleRate:Number = 100.0; + + /** + * If true the IP address of the sender will be anonymized. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see Tracker#ANON_IP Tracker.ANON_IP + */ + public var anonymizeIp:Boolean = false; + + /** + * Allows to override the IP address of the user. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see Tracker#IP_OVERRIDE Tracker.IP_OVERRIDE + */ + public var overrideIpAddress:String = ""; + + /** + * Allows to override the User Agent of the browser. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see Tracker#USER_AGENT_OVERRIDE Tracker.USER_AGENT_OVERRIDE + */ + public var overrideUserAgent:String = ""; + + /** + * Allows to override the geographical location of the user. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see Tracker#GEOGRAPHICAL_OVERRIDE Tracker.GEOGRAPHICAL_OVERRIDE + * @see http://developers.google.com/analytics/devguides/collection/protocol/v1/geoid Geographical Targeting + */ + public var overrideGeographicalId:String = ""; + + + /* read-only */ + + /** + * The URL Endpoint where to send the HTTP requests. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://developers.google.com/analytics/devguides/collection/protocol/v1/reference#endpoint URL Endpoint + */ + public function get endpoint():String { return _endpoint; } + + /** + * The Secure URL Endpoint where to send the HTTPS requests. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://developers.google.com/analytics/devguides/collection/protocol/v1/reference#endpoint URL Endpoint + */ + public function get secureEndpoint():String { return _secureEndpoint; } + + /** + * The length of the entire encoded URL must be no longer than 2000 Bytes. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://developers.google.com/analytics/devguides/collection/protocol/v1/reference#get GET + */ + public function get maxGETlength():uint { return _maxGETlength; } + + /** + * The POST body of a request must be no longer than 8192 bytes. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://developers.google.com/analytics/devguides/collection/protocol/v1/reference#using-post using POST + */ + public function get maxPOSTlength():uint { return _maxPOSTlength; } + + /** + * The name of the storage container. + * + *

+ * A storage could be anything: SharedObject, File, DataBase, etc. + *

+ * + *

+ * In general, the storage will only contain the ClientId and no other informations. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see Tracker#CLIENT_ID Tracker.CLIENT_ID + */ + public function get storageName():String { return _storageName; } + + /** + * Creates a new Configuration. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function Configuration() + { + super(); + } + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracking/HitModel.as b/src/libraries/uanalytics/tracking/HitModel.as new file mode 100644 index 0000000..87301d2 --- /dev/null +++ b/src/libraries/uanalytics/tracking/HitModel.as @@ -0,0 +1,157 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracking +{ + import flash.utils.Dictionary; + + /** + * Core data model class that represents a single "hit". + * + *

+ * Users primarily get and set field values. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public class HitModel + { + private var _data:Dictionary; + private var _metadata:Metadata; + + /** + * Create a new HitModel. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function HitModel() + { + _metadata = new Metadata(); + clear(); + } + + /** + * Set a model field to a specific value. + * + *

+ * Common field names are provided as static members of the Tracker class. + * You may also use your own field names to store information in the data + * model but they will not be sent in the tracking beacon. + *

+ * + * @param field the field to set. May not be null. + * @param value the new field value. May be null. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function set( field:String, value:String ):void + { + _data[ _metadata.getHitModelKey( field ) ] = value; + } + + /** + * Returns the value of the specified field. + * + *

+ * See the set method for a description of possible field + * names. + *

+ * + * @param field the field to return. + * + * @return the value of the specified field. May return null. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function get( field:String ):String + { + return _data[ _metadata.getHitModelKey( field ) ]; + } + + /** + * Add the data of a HitModel to this model. + * + * @param model the model to add. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function add( model:HitModel ):void + { + for( var key:String in model._data ) + { + _data[ key ] = model._data[ key ]; + } + } + + /** + * Returns a new copy of this HitModel with all the same + * fields set. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function clone():HitModel + { + var copy:HitModel = new HitModel(); + + for( var key:String in _data ) + { + copy._data[ key ] = _data[ key ]; + } + + return copy; + } + + /** + * Clear all the fields set in the model. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function clear():void + { + _data = new Dictionary(); + } + + /** + * Returns all of the field names currently set in the model. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function getFieldNames():Vector. + { + var data:Vector. = new Vector.(); + + for( var keys:String in _data ) + { + data.push( keys ); + } + + return data; + } + } + +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracking/HitSampler.as b/src/libraries/uanalytics/tracking/HitSampler.as new file mode 100644 index 0000000..6e4b0a4 --- /dev/null +++ b/src/libraries/uanalytics/tracking/HitSampler.as @@ -0,0 +1,146 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracking +{ + import libraries.uanalytics.utils.isDigit; + import libraries.uanalytics.utils.crc32; + + import flash.utils.ByteArray; + + /** + * Utility methods for determining whether or not a particular hit + * should be sent to Google Analytics collection servers due to sampling. + * + *

+ * Sampling is determined on a per-client basis as identified by + * the Tracker.CLIENT_ID field. + * This means that either all or none of the hits from a particular client + * will be sent. + *

+ * + *

+ * By default we use the value set in Configuration, + * you can edit the configuration to use something else than the + * default (100%). + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see Configuration#sampleRate Configuration.sampleRate + * @see http://support.google.com/analytics/answer/2637192?hl=en How sampling works + */ + public class HitSampler + { + /** + * Return a hash for the specified string. + * + * @param source input string to be hashed. + * @return hash of input string. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + private static function _hashString( source:String ):Number + { + var crc:crc32 = new crc32(); + var bytes:ByteArray = new ByteArray(); + //bytes.writeMultiByte( source, "UTF-8" ); + bytes.writeUTFBytes( source ); + crc.update( bytes ); + return crc.valueOf(); + } + + /** + * Converts a string to a Number. + * Follows almost the same rules as parseFloat() + * except that trailing non-numeric characters are not ignored and return NaN. + * + * @param The string to read and convert to a Number. + * @return A number or NaN (not a number). + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + private static function _parseNumber( str:String ):Number + { + if( str == "" ) { return NaN; } + + var i:uint; + var l:uint = str.length; + var dot:uint = 0; + for( i=0; i + * Sampling is implemented per visitor. + * The sample rate is determined from the SAMPLE_RATE field. + *

+ * + * @param model + * + * @return true if the hit should be sampled out and not sent. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static function isSampled( model:HitModel, samplerate:String = "" ):Boolean + { + var sampleRate:Number = getSampleRate( samplerate ); + + return (sampleRate < 100) && (_hashString( model.get(Tracker.CLIENT_ID) ) % 10000 >= 100 * sampleRate) + } + + /** + * Parse the specified sample rate string expressed as a floating point numbers. + * + *

+ * Values will be rounded to the nearest 100th. + * Invalid numbers will return 0. + * Values greater than 100 will return 100. + *

+ * + * @param rate sample rate string + * + * @return the sample rate as a double + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static function getSampleRate( rate:String ):Number + { + if( (rate == null) || (rate == "") ) { return 100; } + + var result:Number = Math.max( 0, Math.min(100, Math.round( _parseNumber(rate) * 100) / 100) ); + + // An invalid number so sample everything out. + if( isNaN( result ) ) { return 0; } + + return result; + } + } + +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracking/HitSender.as b/src/libraries/uanalytics/tracking/HitSender.as new file mode 100644 index 0000000..2385860 --- /dev/null +++ b/src/libraries/uanalytics/tracking/HitSender.as @@ -0,0 +1,183 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracking +{ + + /** + * HitSender is a base class that will send hit data to Google Analytics servers. + * + *

+ * To be considered as an abstract class to extend from. + *

+ * + * @example Usage + * + * + * // extend from HitSender + * public class TraceHitSender extends HitSender + * { + * + * private var _tracker:AnalyticsTracker; + * + * // pass a tracker as constructor arguments + * public function TraceHitSender( tracker:AnalyticsTracker ) + * { + * super(); + * _tracker = tracker; + * + * // instanciate a URL sending mecanism + * /* Note: + * eg. being able to POST or GET data to a URL + * + * but it could be anything really, for example + * you could save the HitModel data to a file + * ---- + * 2015-10-01 11:10 GET /collect?v=1&tid=UA-123-45&cid=12345 + * 2015-10-01 11:15 POST /collect v=1&tid=UA-123-45&cid=12345 + * ... + * ---- + * and have a kind of cron task later reading trough the file + * and actually sending / logging / tracing / etc. the data + * etc. + * */ + * } + * + * } + * + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracker.senders.BSDSocketHitSender + * @see libraries.uanalytics.tracker.senders.CurlHitSender + * @see libraries.uanalytics.tracker.senders.DebugHitSender + * @see libraries.uanalytics.tracker.senders.LoaderHitSender + * @see libraries.uanalytics.tracker.senders.TraceHitSender + * @see libraries.uanalytics.tracker.senders.URLLoaderHitSender + * @see libraries.uanalytics.tracker.senders.URLStreamSender + */ + public class HitSender implements AnalyticsSender + { + + /** + * Create an empty HitSender. + * + *

+ * Do not use or instanciate this class directly, extend it instead. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function HitSender() + { + super(); + } + + /** + * Adds the named query parameter and value to the specified String. + * This take care of URL encoding special characters. + * + * @param name the HitModel key name. + * @param value the field value. + * + * @return a string formated such as &NAME=VALUE + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected function _addParameter( name:String, value:String ):String + { + var str:String = ""; + str += "&"; + str += _appendEncoded( name.substring(1) ); + str += "="; + str += _appendEncoded( value ); + + return str; + } + + /** + * URL encodes the specified value. + * + * @param value the value to encode. + * + * @return the value URL encoded. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected function _appendEncoded( value:String ):String + { + return encodeURIComponent( value ); + } + + /** + * Build the hit payload from the hit model. The payload can be sent + * via either setting this as the body of an HTTP POST request or + * as the query parameter string of the request. + * + * @param model the HitModel to send. + * + * @return the payload representation that is used to send the hit. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected function _buildHit( model:HitModel ):String + { + var str:String = ""; + + // This library supports version 1 of the tracking API. + str += "v=1"; + + if( Configuration.SDKversion != "" ) + { + // Identify the client library version. + str += _addParameter( "&_v", Configuration.SDKversion ); + } + + var names:Vector. = model.getFieldNames(); + names.sort( Array.CASEINSENSITIVE ); + + var name:String; + var value:String; + var i:uint; + for( i=0; i 0) && + (name.charAt(0) == Metadata.FIELD_PREFIX) ) + { + value = model.get( name ); + if( value != null ) + { + str += _addParameter( name, value ); + } + } + } + + return str; + } + + /** @inheritDoc */ + public function send( model:HitModel ):void + { + //nothing + } + + } + +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracking/Metadata.as b/src/libraries/uanalytics/tracking/Metadata.as new file mode 100644 index 0000000..52d7132 --- /dev/null +++ b/src/libraries/uanalytics/tracking/Metadata.as @@ -0,0 +1,329 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracking +{ + import flash.utils.Dictionary; + + /** + * Defines the set of fields and field generators that comprise a data model. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see Tracker + */ + public class Metadata + { + /** + * The special prefix character used to identify a field name as representing + * a tracking API query parameter name. + * + *

+ * For example, the field named "&sr" specify the screen resolution + * which is sent as the URL query parameter name "sr" in the tracking API. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const FIELD_PREFIX:String = "&"; + + /** + * A map from field name to parameter. + * + *

+ * Eg. "screenResolution" to "&sr". + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + private var _nameToParameterMap:Dictionary = new Dictionary(); + + /** + * A map from a regular expression to a query parameter name. + * + *

+ * Eg. "metric([0-9])+" to "&cm". + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + private var _patternToParameterMap:Dictionary = new Dictionary(); + + /** + * Creates the default Metadata. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function Metadata() + { + super(); + + // General + addAlias( Tracker.PROTOCOL_VERSION, "v" ); + addAlias( Tracker.TRACKING_ID, "tid" ); + addAlias( Tracker.ANON_IP, "aip" ); + addAlias( Tracker.DATA_SOURCE, "ds" ); + addAlias( Tracker.QUEUE_TIME, "qt" ); + addAlias( Tracker.CACHE_BUSTER, "z" ); + + // User + addAlias( Tracker.CLIENT_ID, "cid" ); + addAlias( Tracker.USER_ID, "uid" ); + + // Session + addAlias( Tracker.SESSION_CONTROL, "sc" ); + addAlias( Tracker.IP_OVERRIDE, "uip" ); + addAlias( Tracker.USER_AGENT_OVERRIDE, "ua" ); + addAlias( Tracker.GEOGRAPHICAL_OVERRIDE, "geoid" ); + + // Traffic Sources + addAlias( Tracker.DOCUMENT_REFERRER, "dr" ); + addAlias( Tracker.CAMPAIGN_NAME, "cn" ); + addAlias( Tracker.CAMPAIGN_SOURCE, "cs" ); + addAlias( Tracker.CAMPAIGN_MEDIUM, "cm" ); + addAlias( Tracker.CAMPAIGN_KEYWORD, "ck" ); + addAlias( Tracker.CAMPAIGN_CONTENT, "cc" ); + addAlias( Tracker.CAMPAIGN_ID, "ci" ); + addAlias( Tracker.GOOGLE_ADWORDS_ID, "gclid" ); + addAlias( Tracker.GOOGLE_DISPLAY_ADS_ID, "dclid" ); + + // System Info + addAlias( Tracker.SCREEN_RESOLUTION, "sr" ); + addAlias( Tracker.VIEWPORT_SIZE, "vp" ); + addAlias( Tracker.DOCUMENT_ENCODING, "de" ); + addAlias( Tracker.SCREEN_COLORS, "sd" ); + addAlias( Tracker.USER_LANGUAGE, "ul" ); + addAlias( Tracker.JAVA_ENABLED, "je" ); + addAlias( Tracker.FLASH_VERSION, "fl" ); + + // Hit + addAlias( Tracker.HIT_TYPE, "t" ); + addAlias( Tracker.NON_INTERACTION, "ni" ); + + // Content Information + addAlias( Tracker.DOCUMENT_LOCATION, "dl" ); + addAlias( Tracker.DOCUMENT_HOSTNAME, "dh" ); + addAlias( Tracker.DOCUMENT_PATH, "dp" ); + addAlias( Tracker.DOCUMENT_TITLE, "dt" ); + addAlias( Tracker.SCREEN_NAME, "cd" ); + addAlias( Tracker.LINK_ID, "linkid" ); + + // App Tracking + addAlias( Tracker.APP_NAME, "an" ); + addAlias( Tracker.APP_ID, "aid" ); + addAlias( Tracker.APP_VERSION, "av" ); + addAlias( Tracker.APP_INSTALLER_ID, "aiid" ); + + // Event Tracking + addAlias( Tracker.EVENT_CATEGORY, "ec" ); + addAlias( Tracker.EVENT_ACTION, "ea" ); + addAlias( Tracker.EVENT_LABEL, "el" ); + addAlias( Tracker.EVENT_VALUE, "ev" ); + + // E-Commerce + addAlias( Tracker.TRANSACTION_ID, "ti" ); + addAlias( Tracker.TRANSACTION_AFFILIATION, "ta" ); + addAlias( Tracker.TRANSACTION_REVENUE, "tr" ); + addAlias( Tracker.TRANSACTION_SHIPPING, "ts" ); + addAlias( Tracker.TRANSACTION_TAX, "tt" ); + addAlias( Tracker.ITEM_NAME, "in" ); + addAlias( Tracker.ITEM_PRICE, "ip" ); + addAlias( Tracker.ITEM_QUANTITY, "iq" ); + addAlias( Tracker.ITEM_CODE, "ic" ); + addAlias( Tracker.ITEM_CATEGORY, "iv" ); + addAlias( Tracker.CURRENCY_CODE, "cu" ); + + // Enhanced E-Commerce (not completely supported yet) + // Product SKU + // Product Name + // Product Brand + // Product Category + // Product Variant + // Product Price + // Product Quantity + // Product Coupon Code + // Product Position + // Product Custom Dimension + // Product Custom Metric + addAlias( Tracker.PRODUCT_ACTION, "pa" ); + // Transaction ID - duplicate + // Affiliation - duplicate + // Revenue - duplicate + // Tax - duplicate + // Shipping - duplicate + addAlias( Tracker.COUPON_CODE, "tcc" ); + addAlias( Tracker.PRODUCT_ACTION_LIST, "pal" ); + addAlias( Tracker.CHECKOUT_STEP, "cos" ); + addAlias( Tracker.CHECKOUT_STEP_OPTION, "col" ); + // Product Impression List Name + // Product Impression SKU + // Product Impression Name + // Product Impression Brand + // Product Impression Category + // Product Impression Variant + // Product Impression Position + // Product Impression Price + // Product Impression Custom Dimension + // Product Impression Custom Metric + // Promotion ID + // Promotion Name + // Promotion Creative + // Promotion Position + addAlias( Tracker.PROMOTION_ACTION, "promoa" ); + + // Social Interactions + addAlias( Tracker.SOCIAL_NETWORK, "sn" ); + addAlias( Tracker.SOCIAL_ACTION, "sa" ); + addAlias( Tracker.SOCIAL_TARGET, "st" ); + + // Timing + addAlias( Tracker.USER_TIMING_CATEGORY, "utc" ); + addAlias( Tracker.USER_TIMING_VAR, "utv" ); + addAlias( Tracker.USER_TIMING_TIME, "utt" ); + addAlias( Tracker.USER_TIMING_LABEL, "utl" ); + addAlias( Tracker.PAGE_LOAD_TIME, "plt" ); + addAlias( Tracker.DNS_TIME, "dns" ); + addAlias( Tracker.PAGE_DOWNLOAD_TIME , "pdt" ); + addAlias( Tracker.REDIRECT_RESPONSE_TIME, "rrt" ); + addAlias( Tracker.TCP_CONNECT_TIME, "tcp" ); + addAlias( Tracker.SERVER_RESPONSE_TIME, "srt" ); + addAlias( Tracker.DOM_INTERACTIVE_TIME, "dit" ); + addAlias( Tracker.CONTENT_LOAD_TIME, "clt" ); + + // Exceptions + addAlias( Tracker.EXCEPT_DESCRIPTION, "exd" ); + addAlias( Tracker.EXCEPT_FATAL, "exf" ); + + // Custom Dimensions / Metrics + addPatternAlias( "dimension([0-9]+)", "cd" ); + addPatternAlias( "metric([0-9]+)", "cm" ); + //addPatternAlias( "contentGroup([0-9]+)", "cg" ); // not supported anymore? + + } + + /** + * Given a field name, return the key to use in the data model map based + * on whether the field name matches a registered regular expression. + * + * @param field the field name to find in pattern + * + * @return the key to use in the data model map based on whether the + * field name matches a registered regular expression. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + private function _getKeyFromPattern( field:String ):String + { + for( var pattern:String in _patternToParameterMap ) + { + var re:RegExp = new RegExp( pattern ); + var result:Object = re.exec( field ); + + if( result && result[1] ) + { + var param:String = FIELD_PREFIX + _patternToParameterMap[pattern] + result[1]; + addAlias( field, param ); + return param; + } + } + + return field; + } + + /** + * Add an alias in the data from field name to query parameter. + * + * @param field the field name + * @param parameter the associated parameter + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected function addAlias( field:String, parameter:String ):void + { + _nameToParameterMap[ field ] = FIELD_PREFIX + parameter; + } + + /** + * Add an alias in the data from field name regular expression + * to query parameter. + * + *

+ * The pattern should have one group defined which will capture the + * portion of the field name to use as the suffix for the parameter. + *

+ * + *

+ * For example, the field name "metric34" would match the pattern: + * "metric([0-9])+" with parameter prefix "cm" and would map the field + * to the parameter "cm34" + *

+ * + * @param pattern the pattern to match + * @param parameterPrefix the parameter prefix + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected function addPatternAlias( pattern:String, parameterPrefix:String ):void + { + _patternToParameterMap[ pattern ] = parameterPrefix; + } + + /** + * Given a field name, return the key to use in the data model map + * for which to store the field value. + * + * @param field the field name + * + * @return the key to use in the data model map + * for which to store the field value. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function getHitModelKey( field:String ):String + { + if( (field.length > 0) && (field.charAt(0) == FIELD_PREFIX) ) + { + return field; + } + + var key:String = _nameToParameterMap[ field ]; + + if( key == null ) + { + key = _getKeyFromPattern( field ); + } + + return key; + } + + } + +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracking/RateLimitError.as b/src/libraries/uanalytics/tracking/RateLimitError.as new file mode 100644 index 0000000..6f76743 --- /dev/null +++ b/src/libraries/uanalytics/tracking/RateLimitError.as @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracking +{ + /** + * An error that will be thrown when hits are being sent at too great a rate. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public class RateLimitError extends Error + { + prototype.name = "RateLimitError"; + + /** + * Creates a RateLimitError. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function RateLimitError( message:String = "", id:int = 0 ) + { + super( message, id ); + this.name = prototype.name; + } + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracking/RateLimiter.as b/src/libraries/uanalytics/tracking/RateLimiter.as new file mode 100644 index 0000000..80a7518 --- /dev/null +++ b/src/libraries/uanalytics/tracking/RateLimiter.as @@ -0,0 +1,131 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracking +{ + import flash.utils.getTimer; + + /** + * Implements a rate limiting mecanism that ensures the tracker + * does not send too many hits at once. + * + *

+ * The mecanism is based on the token bucket algorithm, + * and allows you to send bursts of hits to Google Analytics, + * while preventing clients from sending data too quickly. + *

+ * + *

+ * Each tracker has a maximum limit for the number of requests it can send concurrently. + * The tracker also maintain a count of the number of concurrent hits that have been sent. + * As a hit is sent to Google Analytics, the count decreases by one. + * When the count is 0, the maximum limit has been reached, + * and no new requests are sent. + * Then over a small period of time, the count is increased back to its original limit, + * allowing data to be sent again. + *

+ * + *

+ * The limiting rate is specified in each individual trackers. + * For the WebTracker, we follow what is done with analytics.js, + * the tracker starts with 20 hits that are replenished at a rate of 2 hits per second. + * For the ApplicationTracker, we follow what is done with the Android SDK and the iOS SDK, + * each trackers starts with 60 hits that are replenished at a rate of 1 hit every 2 seconds. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://en.wikipedia.org/wiki/Token_bucket Token bucket + */ + public class RateLimiter + { + private var _capacity:int; + private var _rate:int; + private var _span:Number; + private var _lastTime:int; + private var _tokenCount:int; + + /** + * Create a new RateLimiter with the specified maximum token capacity + * and a specified number of new tokens generated per second (rate). + * + *

+ * The token count is initialized to the maximum token capacity. + *

+ * + * @param capacity maximum number of accumulated tokens. + * @param rate the number of additional tokens to regenerate per second. + * @param span the time span to generate tokens (default to 1 second) + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function RateLimiter( capacity:int, rate:int, span:Number = 1 ) + { + _capacity = capacity; + _rate = rate; + _span = span; + _tokenCount = capacity; + _lastTime = now(); + } + + /** + * Returns the number of milliseconds that have elapsed since the + * Flash runtime virtual machine for ActionScript 3.0 (AVM2) started. + * + *

+ * Used internally for generating tokens and useful for testing purposes. + *

+ * + * @return a relative time in milliseconds. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected function now():int + { + return getTimer(); + } + + /** + * Attempt to consume a token and return true if successful. + * + *

+ * A return value of false indicates that tokens are being consumed in excess + * of the defined limits. + *

+ * + * @return true if the rate limit has not been exceeded. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function consumeToken():Boolean + { + var now:int = now(); + var newTokens:int = Math.max( 0, (now - _lastTime) * ( (_rate * _span) / 1000) ); + _tokenCount = Math.min( _tokenCount + newTokens, _capacity ); + + if( _tokenCount > 0 ) + { + _tokenCount--; + _lastTime = now; + return true; + } + + return false; + } + + } + +} \ No newline at end of file diff --git a/src/libraries/uanalytics/tracking/Tracker.as b/src/libraries/uanalytics/tracking/Tracker.as new file mode 100644 index 0000000..4129ce9 --- /dev/null +++ b/src/libraries/uanalytics/tracking/Tracker.as @@ -0,0 +1,1936 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package libraries.uanalytics.tracking +{ + + import flash.utils.Dictionary; + + /** + * A base class for the Google Analytics tracker. + * + *

+ * To be considered as an abstract class that provides + * measurement protocol contants and utility methods + * related to the HitModel. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public class Tracker implements AnalyticsTracker + { + + // General + /** + * Required for all hit types. + *

+ * The Protocol version. + *

+ * + *

+ * The current value is '1'. + * This will only change when there are changes made that are not + * backwards compatible. + *

+ * + * @example + * v=1 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const PROTOCOL_VERSION:String = "protocolVersion"; + + /** + * Required for all hit types. + *

+ * The tracking ID / web property ID. + *

+ * + *

+ * The format is UA-XXXX-Y. + * All collected data is associated by this ID. + *

+ * + * @example + * tid=UA-XXXX-Y + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const TRACKING_ID:String = "trackingId"; + + /** + * Optional. + *

+ * When present, the IP address of the sender will be anonymized. + *

+ * + *

+ * For example, the IP will be anonymized if any of the following + * parameters are present in the payload: &aip=, + * &aip=0, or &aip=1 + *

+ * + * @example + * aip=1 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const ANON_IP:String = "anonymizeIp"; + + /** + * Optional. + *

+ * Indicates the data source of the hit. + *

+ * + *

+ * Hits sent from analytics.js will have data source set to 'web'; + * hits sent from one of the mobile SDKs will have data source set to 'app'. + *

+ * + * @example + * ds=web, + * ds=app. + * ds=call%20center, + * ds=crm + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const DATA_SOURCE:String = "dataSource"; + + /** + * Optional. + *

+ * Used to collect offline / latent hits. + *

+ * + *

+ * The value represents the time delta (in milliseconds) between when + * the hit being reported occurred and the time the hit was sent. + * The value must be greater than or equal to 0. + * Values greater than four hours may lead to hits not being processed. + *

+ * + * @example + * qt=560 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const QUEUE_TIME:String = "queueTime"; + + /** + * Optional. + *

+ * Used to send a random number in GET requests to ensure browsers and + * proxies don't cache hits. + *

+ * + *

+ * It should be sent as the final parameter of the request since we've + * seen some 3rd party internet filtering software add additional + * parameters to HTTP requests incorrectly. + * This value is not used in reporting. + *

+ * + * @example + * z=289372387623 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const CACHE_BUSTER:String = "cacheBuster"; + + + // User + + /** + * Required for all hit types. + *

+ * The Client ID anonymously identifies a particular user, device, + * or browser instance. + *

+ * + *

+ * For the web, this is generally stored as a first-party cookie with a + * two-year expiration. + * For mobile apps, this is randomly generated for each particular + * instance of an application install. + * The value of this field should be a random UUID (version 4) as + * described in rfc4122. + *

+ * + * @example + * cid=35009a79-1a05-49d7-b876-2b884d0f825b + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const CLIENT_ID:String = "clientId"; + + /** + * Optional. + *

+ * This is intended to be a known identifier for a user provided by + * the site owner/tracking library user. + *

+ * + *

+ * It must not itself be PII (personally identifiable information). + * The value should never be persisted in GA cookies or other Analytics + * provided storage. + *

+ * + * @example + * uid=as8eknlll + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const USER_ID:String = "userId"; + + + // Session + + /** + * Optional. + *

+ * Used to control the session duration. + *

+ * + *

+ * A value of 'start' forces a new session to start with this hit + * and 'end' forces the current session to end with this hit. + * All other values are ignored. + *

+ * + * @example + * sc=start, sc=end + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const SESSION_CONTROL:String = "sessionControl"; + + /** + * Optional. + *

+ * The IP address of the user. + *

+ * + *

+ * This should be a valid IP address in IPv4 or IPv6 format. + * It will always be anonymized just as though aip (anonymize IP) + * had been used. + *

+ * + * @example + * uip=1.2.3.4 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const IP_OVERRIDE:String = "ipOverride"; + + /** + * Optional. + *

+ * The User Agent of the browser. + *

+ * + *

+ * Note that Google has libraries to identify real user agents. + * Hand crafting your own agent could break at any time. + *

+ * + * @example + * ua=Opera%2F9.80%20%28Windows%20NT%206.0%29%20Presto%2F2.12.388%20Version%2F12.14 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const USER_AGENT_OVERRIDE:String = "userAgentOverride"; + + /** + * Optional. + *

+ * The geographical location of the user. + *

+ * + *

+ * The geographical ID should be a two letter country code or a + * criteria ID representing a city or region + * (see geoid). + * This parameter takes precedent over any location derived from IP + * address, including the IP Override parameter. + * An invalid code will result in geographical dimensions to be set to + * '(not set)'. + *

+ * + * @example + * geoid=21137, geoid=US + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const GEOGRAPHICAL_OVERRIDE:String = "geographicalOverride"; + + + // Traffic Sources + + /** + * Optional. + *

+ * Specifies which referral source brought traffic to a website. + *

+ * + *

+ * This value is also used to compute the traffic source. + * The format of this value is a URL. + *

+ * + * @example + * dr=http%3A%2F%2Fexample.com + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const DOCUMENT_REFERRER:String = "documentReferrer"; + + /** + * Optional. + *

+ * Specifies the campaign name. + *

+ * + * @example + * cn=%28direct%29 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const CAMPAIGN_NAME:String = "campaignName"; + + /** + * Optional. + *

+ * Specifies the campaign source. + *

+ * + * @example + * cs=%28direct%29 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const CAMPAIGN_SOURCE:String = "campaignSource"; + + /** + * Optional. + *

+ * Specifies the campaign medium. + *

+ * + * @example + * cm=organic + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const CAMPAIGN_MEDIUM:String = "campaignMedium"; + + /** + * Optional. + *

+ * Specifies the campaign keyword. + *

+ * + * @example + * ck=Blue%20Shoes + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const CAMPAIGN_KEYWORD:String = "campaignKeyword"; + + /** + * Optional. + *

+ * Specifies the campaign content. + *

+ * + * @example + * cc=content + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const CAMPAIGN_CONTENT:String = "campaignContent"; + + /** + * Optional. + *

+ * Specifies the campaign ID. + *

+ * + * @example + * ci=ID + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const CAMPAIGN_ID:String = "campaignId"; + + /** + * Optional. + *

+ * Specifies the Google AdWords Id. + *

+ * + * @example + * gclid=CL6Q-OXyqKUCFcgK2goddQuoHg + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const GOOGLE_ADWORDS_ID:String = "googleAdwordsId"; + + /** + * Optional. + *

+ * Specifies the Google Display Ads Id. + *

+ * + * @example + * dclid=d_click_id + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const GOOGLE_DISPLAY_ADS_ID:String = "googleDisplayAdsId"; + + + // System Info + + /** + * Optional. + *

+ * Specifies the screen resolution. + *

+ * + * @example + * sr=800x600 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const SCREEN_RESOLUTION:String = "screenResolution"; + + /** + * Optional. + *

+ * Specifies the viewable area of the browser / device. + *

+ * + * @example + * vp=123x456 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const VIEWPORT_SIZE:String = "viewportSize"; + + /** + * Optional. + *

+ * Specifies the character set used to encode the page / document. + *

+ * + * @example + * de=UTF-8 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const DOCUMENT_ENCODING:String = "documentEncoding"; + + /** + * Optional. + *

+ * Specifies the screen color depth. + *

+ * + * @example + * sd=24-bits + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const SCREEN_COLORS:String = "screenColors"; + + /** + * Optional. + *

+ * Specifies the language. + *

+ * + * @example + * ul=en-us + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const USER_LANGUAGE:String = "userLanguage"; + + /** + * Optional. + *

+ * Specifies whether Java was enabled. + *

+ * + * @example + * je=1 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const JAVA_ENABLED:String = "javaEnabled"; + + /** + * Optional. + *

+ * Specifies the flash version. + *

+ * + * @example + * fl=10%201%20r103 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const FLASH_VERSION:String = "flashVersion"; + + + // Hit + + /** + * Required for all hit types. + *

+ * The type of hit. Must be one of 'pageview', 'screenview', 'event', + * 'transaction', 'item', 'social', 'exception', 'timing'. + *

+ * + * @example + * t=pageview + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const HIT_TYPE:String = "hitType"; + + /** + * Optional. + *

+ * Specifies that a hit be considered non-interactive. + *

+ * + * @example + * ni=1 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const NON_INTERACTION:String = "nonInteraction"; + + + // Content Information + + /** + * Optional. + *

+ * Use this parameter to send the full URL (document location) of the + * page on which content resides. + *

+ * + *

+ * You can use the dh and dp parameters to + * override the hostname and path + query portions of the document + * location, accordingly. + * The JavaScript clients determine this parameter using the + * concatenation of the document.location.origin + + * document.location.pathname + document.location.search browser + * parameters. + * Be sure to remove any user authentication or other private information + * from the URL if present. + * + * For 'pageview' hits, either dl + * or both dh and dp have to be specified for + * the hit to be valid. + *

+ * + * @example + * dl=http%3A%2F%2Ffoo.com%2Fhome%3Fa%3Db + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const DOCUMENT_LOCATION:String = "documentLocation"; + + /** + * Optional. + *

+ * Specifies the hostname from which content was hosted. + *

+ * + * @example + * dh=foo.com + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const DOCUMENT_HOSTNAME:String = "documentHostname"; + + /** + * Optional. + *

+ * The path portion of the page URL. + *

+ * + *

+ * Should begin with '/'. + * For 'pageview' hits, either dl + * or both dh and dp have to be specified for + * the hit to be valid. + *

+ * + * @example + * dp=%2Ffoo + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const DOCUMENT_PATH:String = "documentPath"; + + /** + * Optional. + *

+ * The title of the page / document. + *

+ * + * @example + * dt=Settings + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const DOCUMENT_TITLE:String = "documentTitle"; + + /** + * Required for all hit types. + * + *

+ * This parameter is optional on web properties, and required on mobile + * properties for screenview hits, where it is used for the 'Screen Name' + * of the screenview hit. + * + * On web properties this will default to the unique URL of the page by + * either using the dl parameter as-is + * or assembling it from dh and dp. + *

+ * + * @example + * cd=High%20Scores + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const SCREEN_NAME:String = "screenName"; //also: Content Description + + /** + * Optional. + *

+ * The ID of a clicked DOM element. + *

+ * + *

+ * Used to disambiguate multiple links to the same URL in + * In-Page Analytics reports when Enhanced Link Attribution is enabled + * for the property. + *

+ * + * @example + * linkid=nav_bar + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const LINK_ID:String = "linkId"; + + + // App Tracking + + /** + * Required for all hit types. + *

+ * Specifies the application name. + *

+ * + *

+ * This field is required for all hit types sent to app properties. + * For hits sent to web properties, this field is optional. + *

+ * + * @example + * an=My%20App + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const APP_NAME:String = "appName"; + + /** + * Optional. + *

+ * Application identifier. + *

+ * + * @example + * aid=com.company.app + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const APP_ID:String = "appId"; + + /** + * Optional. + *

+ * Specifies the application version. + *

+ * + * @example + * av=1.2 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const APP_VERSION:String = "appVersion"; + + /** + * Optional. + *

+ * Application installer identifier. + *

+ * + * @example + * aiid=com.platform.vending + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const APP_INSTALLER_ID:String = "appInstallerId"; + + + // Event Tracking + + /** + * Required for event hit type. + *

+ * Specifies the event category. + *

+ * + *

+ * Must not be empty. + *

+ * + * @example + * ec=Category + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const EVENT_CATEGORY:String = "eventCategory"; + + /** + * Required for event hit type. + *

+ * Specifies the event action + *

+ * + *

+ * Must not be empty. + *

+ * + * @example + * ea=Action + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const EVENT_ACTION:String = "eventAction"; + + /** + * Optional. + *

+ * Specifies the event label. + *

+ * + * @example + * el=Label + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const EVENT_LABEL:String = "eventLabel"; + + /** + * Optional. + *

+ * Specifies the event value. + *

+ * + *

+ * Values must be non-negative. + *

+ * + * @example + * ev=55 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const EVENT_VALUE:String = "eventValue"; + + + // E-Commerce + + /** + * Required for transaction hit type. + *

Required for item hit type.

+ *

+ * A unique identifier for the transaction. + *

+ * + *

+ * This value should be the same for both the Transaction hit and + * Items hits associated to the particular transaction. + *

+ * + * @example + * ti=OD564 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const TRANSACTION_ID:String = "transactionId"; + + /** + * Optional. + *

+ * Specifies the affiliation or store name. + *

+ * + * @example + * ta=Member + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const TRANSACTION_AFFILIATION:String = "transactionAffiliation"; + + /** + * Optional. + *

+ * Specifies the total revenue associated with the transaction. + *

+ * + *

+ * This value should include any shipping or tax costs. + *

+ * + * @example + * tr=15.47 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const TRANSACTION_REVENUE:String = "transactionRevenue"; + + /** + * Optional. + *

+ * Specifies the total shipping cost of the transaction. + *

+ * + * @example + * ts=3.50 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const TRANSACTION_SHIPPING:String = "transactionShipping"; + + /** + * Optional. + *

+ * Specifies the total tax of the transaction. + *

+ * + * @example + * tt=11.20 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const TRANSACTION_TAX:String = "transactionTax"; + + /** + * Required for item hit type. + *

+ * Specifies the item name. + *

+ * + * @example + * in=Shoe + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const ITEM_NAME:String = "itemName"; + + /** + * Optional. + *

+ * Specifies the price for a single item / unit. + *

+ * + * @example + * ip=3.50 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const ITEM_PRICE:String = "itemPrice"; + + /** + * Optional. + *

+ * Specifies the number of items purchased. + *

+ * + * @example + * iq=4 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const ITEM_QUANTITY:String = "itemQuantity"; + + /** + * Optional. + *

+ * Specifies the SKU or item code. + *

+ * + * @example + * ic=SKU47 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const ITEM_CODE:String = "itemCode"; + + /** + * Optional. + *

+ * Specifies the category that the item belongs to. + *

+ * + * @example + * iv=Blue + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const ITEM_CATEGORY:String = "itemCategory"; + + /** + * Optional. + *

+ * When present indicates the local currency for all transaction + * currency values. + *

+ * + *

+ * Value should be a valid ISO 4217 currency code. + *

+ * + * @example + * cu=EUR + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const CURRENCY_CODE:String = "currencyCode"; + + // Enhanced E-Commerce (not completely supported yet) + + // Product SKU + // Product Name + // Product Brand + // Product Category + // Product Variant + // Product Price + // Product Quantity + // Product Coupon Code + // Product Position + // Product Custom Dimension + // Product Custom Metric + + /** + * Optional. + *

+ * The role of the products included in a hit. + *

+ * + *

+ * If a product action is not specified, all product definitions + * included with the hit will be ignored. + * Must be one of: detail, click, add, remove, checkout, checkout_option, + * purchase, refund. + * + * For analytics.js the Enhanced Ecommerce plugin must be installed + * before using this field. + *

+ * + * @example + * pa=detail + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const PRODUCT_ACTION:String = "productAction"; + + // Transaction ID - duplicate + // Affiliation - duplicate + // Revenue - duplicate + // Tax - duplicate + // Shipping - duplicate + + /** + * Optional. + *

+ * The transaction coupon redeemed with the transaction. + *

+ * + *

+ * This is an additional parameter that can be sent when Product Action + * is set to 'purchase' or 'refund'. + * + * For analytics.js the Enhanced Ecommerce plugin must be installed + * before using this field. + *

+ * + * @example + * tcc=SUMMER08 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const COUPON_CODE:String = "couponCode"; + + /** + * Optional. + *

+ * The list or collection from which a product action occurred. + *

+ * + *

+ * This is an additional parameter that can be sent when Product Action + * is set to 'detail' or 'click'. + * + * For analytics.js the Enhanced Ecommerce plugin must be installed + * before using this field. + *

+ * + * @example + * pal=Search%20Results + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const PRODUCT_ACTION_LIST:String = "productActionList"; + + /** + * Optional. + *

+ * The step number in a checkout funnel. + *

+ * + *

+ * This is an additional parameter that can be sent when Product Action + * is set to 'checkout'. + * + * For analytics.js the Enhanced Ecommerce plugin must be installed + * before using this field. + *

+ * + * @example + * cos=2 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const CHECKOUT_STEP:String = "checkoutStep"; + + /** + * Optional. + *

+ * Additional information about a checkout step. + *

+ * + *

+ * This is an additional parameter that can be sent when Product Action + * is set to 'checkout'. + * + * For analytics.js the Enhanced Ecommerce plugin must be installed + * before using this field. + *

+ * + * @example + * col=Visa + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const CHECKOUT_STEP_OPTION:String = "checkoutStepOption"; + + // Product Impression List Name + // Product Impression SKU + // Product Impression Name + // Product Impression Brand + // Product Impression Category + // Product Impression Variant + // Product Impression Position + // Product Impression Price + // Product Impression Custom Dimension + // Product Impression Custom Metric + // Promotion ID + // Promotion Name + // Promotion Creative + // Promotion Position + + /** + * Optional. + *

+ * Specifies the role of the promotions included in a hit. + *

+ * + *

+ * If a promotion action is not specified, the default promotion action, + * 'view', is assumed. + * To measure a user click on a promotion set this to 'promo_click'. + * + * For analytics.js the Enhanced Ecommerce plugin must be installed + * before using this field. + *

+ * + * @example + * promoa=click + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const PROMOTION_ACTION:String = "promotionAction"; + + + // Social Interactions + + /** + * Required for social hit type. + *

+ * Specifies the social network. + *

+ * + *

+ * For example Facebook or Google Plus. + *

+ * + * @example + * sn=facebook + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const SOCIAL_NETWORK:String = "socialNetwork"; + + /** + * Required for social hit type. + *

+ * Specifies the social interaction action. + *

+ * + *

+ * For example on Google Plus when a user clicks the +1 button, + * the social action is 'plus'. + *

+ * + * @example + * sa=like + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const SOCIAL_ACTION:String = "socialAction"; + + /** + * Required for social hit type. + *

+ * Specifies the target of a social interaction. + *

+ * + *

+ * This value is typically a URL but can be any text. + *

+ * + * @example + * st=http%3A%2F%2Ffoo.com + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const SOCIAL_TARGET:String = "socialTarget"; + + + // Timing + + /** + * Required for timing hit type. + *

+ * Specifies the user timing category. + *

+ * + * @example + * utc=category + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const USER_TIMING_CATEGORY:String = "userTimingCategory"; + + /** + * Required for timing hit type. + *

+ * Specifies the user timing variable. + *

+ * + * @example + * utv=lookup + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const USER_TIMING_VAR:String = "userTimingVar"; + + /** + * Required for timing hit type. + *

+ * Specifies the user timing value. + *

+ * + *

+ * The value is in milliseconds. + *

+ * + * @example + * utt=123 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const USER_TIMING_TIME:String = "userTimingTime"; + + /** + * Optional. + *

+ * Specifies the user timing label. + *

+ * + * @example + * utl=label + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const USER_TIMING_LABEL:String = "userTimingLabel"; + + /** + * Optional. + *

+ * Specifies the time it took for a page to load. + *

+ * + *

+ * The value is in milliseconds. + *

+ * + * @example + * plt=3554 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const PAGE_LOAD_TIME:String = "pageLoadTime"; + + /** + * Optional. + *

+ * Specifies the time it took to do a DNS lookup. + *

+ * + *

+ * The value is in milliseconds. + *

+ * + * @example + * dns=43 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const DNS_TIME:String = "dnsTime"; + + /** + * Optional. + *

+ * Specifies the time it took for the page to be downloaded. + *

+ * + *

+ * The value is in milliseconds. + *

+ * + * @example + * pdt=500 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const PAGE_DOWNLOAD_TIME:String = "pageDownloadTime"; + + /** + * Optional. + *

+ * Specifies the time it took for any redirects to happen. + *

+ * + *

+ * The value is in milliseconds. + *

+ * + * @example + * rrt=500 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const REDIRECT_RESPONSE_TIME:String = "redirectResponseTime"; + + /** + * Optional. + *

+ * Specifies the time it took for a TCP connection to be made. + *

+ * + *

+ * The value is in milliseconds. + *

+ * + * @example + * tcp=500 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const TCP_CONNECT_TIME:String = "tcpConnectTime"; + + /** + * Optional. + *

+ * Specifies the time it took for the server to respond after the + * connect time. + *

+ * + *

+ * The value is in milliseconds. + *

+ * + * @example + * srt=500 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const SERVER_RESPONSE_TIME:String = "serverResponseTime"; + + /** + * Optional. + *

+ * Specifies the time it took for Document.readyState + * to be 'interactive'. + *

+ * + *

+ * The value is in milliseconds. + *

+ * + * @example + * dit=500 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const DOM_INTERACTIVE_TIME:String = "domInteractiveTime"; + + /** + * Optional. + *

+ * Specifies the time it took for the DOMContentLoaded Event + * to fire. + *

+ * + *

+ * The value is in milliseconds. + *

+ * + * @example + * clt=500 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const CONTENT_LOAD_TIME:String = "contentLoadTime"; + + + // Exceptions + + /** + * Optional. + *

+ * Specifies the description of an exception. + *

+ * + * @example + * exd=DatabaseError + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const EXCEPT_DESCRIPTION:String = "exceptionDescription"; + + /** + * Optional. + *

+ * Specifies whether the exception was fatal. + *

+ * + * @example + * exf=0 + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static const EXCEPT_FATAL:String = "exceptionFatal"; + + + // Custom Dimensions / Metrics + + /** + * Optional. + *

+ * Each custom dimension has an associated index. + *

+ * + *

+ * There is a maximum of 20 custom dimensions (200 for Premium accounts). + * The dimension index must be a positive integer between 1 and 200, inclusive. + *

+ * + *

+ * Dimensions describe data, it answers "What?" + * (what keyword did they use, what city is the visitor from). + *

+ * + * @example + * cd<dimensionIndex>=Sports + * + * @example Usage + * + * tracker.set( "dimension1", "aaa" ); // cd1=aaa + * tracker.set( "dimension2", "bbb" ); // cd2=bbb + * tracker.set( Tracker.CUSTOM_DIMENSION(3), "ccc" ); // cd3=ccc + * + * + * @param index the dimension associated index + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static function CUSTOM_DIMENSION( index:uint = 0 ):String + { + if( index < 1 ) { index = 1; } + + if( index > 200 ) { index = 200; } + + return "dimension" + index; + } + + /** + * Optional. + *

+ * Each custom metric has an associated index. + *

+ * + *

+ * There is a maximum of 20 custom metrics (200 for Premium accounts). + * The metric index must be a positive integer between 1 and 200, inclusive. + *

+ * + *

+ * Metrics measure data, it answers "How long?", "How many?" + * (how many sessions). + *

+ * + * @example + * cm<metricIndex>=47 + * + * @example Usage + * + * tracker.set( "metric1", "ddd" ); // cm1=ddd + * tracker.set( "metric2", "eee" ); // cm2=eee + * tracker.set( Tracker.CUSTOM_METRIC(3), "fff" ); // cm3=fff + * + * + * @param index the metric associated index + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public static function CUSTOM_METRIC( index:uint = 0 ):String + { + if( index < 1 ) { index = 1; } + + if( index > 200 ) { index = 200; } + + return "metric" + index; + } + + /** + * The data model containing the hit. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected var _model:HitModel; + + /** + * The temporary data model for one time fields. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected var _temporary:HitModel; + + /** + * The sender which send the hit data to Google Analytics servers. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected var _sender:HitSender; + + /** + * The limiter which ensure not too many hits are send at once. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected var _limiter:RateLimiter; + + /** + * The configuration of the tracker. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + protected var _config:Configuration; + + /** + * Create an empty Tracker. + * + *

+ * Do not use or instanciate this class directly, extend it instead. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see libraries.uanalytics.tracker.DefaultTracker + */ + public function Tracker() + { + super(); + } + + /** @inheritDoc */ + public function get trackingId():String + { + return get( TRACKING_ID ); + } + + /** @inheritDoc */ + public function get clientId():String + { + return get( CLIENT_ID ); + } + + /** @inheritDoc */ + public function get config():Configuration + { + return _config; + } + + /** + * Set the specified model field to the specified value. + * + * @param field the field to set. May not be null. + * @param value the new field value. May be null. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function set( field:String, value:String ):void + { + if( _model ) + { + _model.set( field, value ); + } + } + + /** + * Set the specified model field to the specified value + * for a one time use. + * + *

+ * The field will be consumed on next hit and then will be reset. + *

+ * + * @param field the field to set. May not be null. + * @param value the new field value. May be null. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function setOneTime( field:String, value:String ):void + { + if( _temporary ) + { + _temporary.set( field, value ); + } + } + + /** + * Return the value of the specified field. + * + * @param field the field to get. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function get( field:String ):String + { + if( _model ) + { + return _model.get( field ); + } + + return null; + } + + /** + * Set multiple field values as specified in the Dictionary. + * + * @param values field/value pairs to set in the model. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function add( values:Dictionary ):void + { + if( _model ) + { + for( var entry:String in values ) + { + set( entry, values[entry] ); + } + } + } + + /** @inheritDoc */ + public function send( hitType:String = null, tempValues:Dictionary = null ):Boolean + { + return false; + } + + /** @inheritDoc */ + public function pageview( path:String, title:String = "" ):Boolean + { + return false; + } + + /** @inheritDoc */ + public function screenview( name:String, appinfo:Dictionary = null ):Boolean + { + return false; + } + + /** @inheritDoc */ + public function event( category:String, action:String, + label:String = "", value:int = -1 ):Boolean + { + return false; + } + + /** @inheritDoc */ + public function transaction( id:String, + affiliation:String = "", + revenue:Number = 0, + shipping:Number = 0, + tax:Number = 0, + currency:String = "" ):Boolean + { + return false; + } + + /** @inheritDoc */ + public function item( transactionId:String, name:String, + price:Number = 0, + quantity:int = 0, + code:String = "", + category:String = "", + currency:String = "" ):Boolean + { + return false; + } + + /** @inheritDoc */ + public function social( network:String, action:String, target:String ):Boolean + { + return false; + } + + /** @inheritDoc */ + public function exception( description:String = "", + isFatal:Boolean = true ):Boolean + { + return false; + } + + /** @inheritDoc */ + public function timing( category:String, name:String, value:int, + label:String = "", + timinginfo:Dictionary = null ):Boolean + { + return false; + } + } + +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/crc32.as b/src/libraries/uanalytics/utils/crc32.as new file mode 100644 index 0000000..e28fd4c --- /dev/null +++ b/src/libraries/uanalytics/utils/crc32.as @@ -0,0 +1,188 @@ +package libraries.uanalytics.utils +{ + import flash.utils.ByteArray; + import flash.utils.Endian; + + /** + * A class to compute the CRC-32 checksum of a data stream. + * + *

+ * Other names: CRC-32/ADCCP, PKZIP + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see https://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks Computation of cyclic redundancy checks + */ + public final class crc32 + { + + private static var lookup:Vector. = make_crc_table(); + + private static function make_crc_table():Vector. + { + var table:Vector. = new Vector.(); + + var c:uint; + var i:uint; + var j:uint; + + for( i=0; i < 256; i++ ) + { + c = i; + for( j=0; j < 8; j++ ) + { + if( (c & 0x00000001) != 0 ) + { + c = (c >>> 1) ^ _poly; + } + else + { + c = (c >>> 1); + } + } + table[i] = c; + } + + return table; + } + + // ---- CONFIG ---- + + private static var _poly:uint = 0xedb88320; + private static var _init:uint = 0xffffffff; + + // ---- CONFIG ---- + + private var _crc:uint; + private var _length:uint; + private var _endian:String; + + /** + * Creates a CRC-32 object. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function crc32() + { + _length = 0xffffffff; + _endian = Endian.LITTLE_ENDIAN; + reset(); + } + + /** + * Returns the byte order for the CRC. + * + *

+ * Either Endian.BIG_ENDIAN for "Most significant bit first" + * or Endian.LITTLE_ENDIAN for "Least significant bit first". + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://en.wikipedia.org/wiki/Computation_of_CRC#Bit_ordering_.28Endianness.29 Bit ordering (endianness) + */ + public function get endian():String { return _endian; } + + /** + * Returns the length the CRC; + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function get length():uint { return _length; } + + /** + * Updates the CRC-32 with a specified array of bytes. + * + * @param bytes The ByteArray object + * @param offset A zero-based index indicating the position into the + * array to begin reading. + * @param length An unsigned integer indicating how far into the buffer + * to read (if 0, the length of the ByteArray is used). + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function update( bytes:ByteArray, offset:uint = 0, length:uint = 0 ):void + { + if( length == 0 ) { length = bytes.length; } + + bytes.position = offset; + + var i:uint; + var c:uint; + var crc:uint = _length & (_crc); + + for( i = offset; i < length; i++ ) + { + c = uint( bytes[ i ] ); + crc = (crc >>> 8) ^ lookup[(crc ^ c) & 0xff]; + } + + _crc = ~crc; + } + + /** + * Resets the CRC-32 to its initial value. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function reset():void + { + _crc = _init; + } + + /** + * Returns the primitive value type of the CRC-32 object (unsigned integer). + * + * @return a 32bits digest + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function valueOf():uint + { + return _crc; + } + + /** + * Returns the string representation of the CRC-32 value. + * + * @param radix Specifies the numeric base (from 2 to 36) to use for + * the uint-to-string conversion. If you do not specify the + * radix parameter, the default value is 16. + * + * @return The numeric representation of the CRC-32 object as a string. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function toString( radix:Number = 16 ):String + { + return _crc.toString( radix ); + } + + } + +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/generateAIRAppInfo.as b/src/libraries/uanalytics/utils/generateAIRAppInfo.as new file mode 100644 index 0000000..74580c0 --- /dev/null +++ b/src/libraries/uanalytics/utils/generateAIRAppInfo.as @@ -0,0 +1,163 @@ +package libraries.uanalytics.utils +{ + import flash.desktop.NativeApplication; + import flash.system.Capabilities; + + import libraries.uanalytics.tracker.ApplicationInfo; + + /** + * Utility function to gather automatically the ApplicationInfo + * from the AIR application descriptor. + * + * @param installerID provide a string for the installer id (optional). + * @param useVersionLabel option flag to use the version label + * over the version number (default to true). + * + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff1.html AIR application descriptor files + * @see http://help.adobe.com/en_US/air/build/WSD079A3A2-1B38-4543-A792-06594E4325FE.html Localizing the application name and description in the AIR application installer + * @see http://blog.dannypatterson.com/2010/03/namespaces-on-attributes-with-e4x-in-actionscript-3/ Namespaces on Attributes with E4X in ActionScript 3 + */ + public function generateAIRAppInfo( installerID:String = "", + useVersionLabel:Boolean = true ):ApplicationInfo + { + var appinfo:ApplicationInfo = new ApplicationInfo(); + + var na:NativeApplication = NativeApplication.nativeApplication; + var app_name:String = ""; + var app_id:String = ""; + var app_version:String = ""; + + if( na ) + { + var descriptor:XML = na.applicationDescriptor; + + // we get the default namespace, eg. + Sample 1.0 + Échantillon 1.0 + Stichprobe 1.0 + + + see: + Localizing the application name and description in the AIR application installer + http://help.adobe.com/en_US/air/build/WSD079A3A2-1B38-4543-A792-06594E4325FE.html + */ + + // first we use the default system language + var lang:String = Capabilities.language; + + /* Note: + the peculiarities of the xml:lang + + even if your node is defined like that + Sample 1.0 + + the xml namespace autoresolve to "http://www.w3.org/XML/1998/namespace" + and if you were to test it with + trace( descriptor.toXMLString() ); + you would see + Sample 1.0 + + WAT? aaa:lang and not xml:lang ? + yes "xml" is a namespace, but the root XML node does not define it + and so for any namespace reference not resolved the naming starts + with aaa, then aab, then aac, etc. + + we could force the namespace reference to be found with + var xml:Namespace = new Namespace("xml", "http://www.w3.org/XML/1998/namespace"); + descriptor.addNamespace(xml); + trace( descriptor.toXMLString() ); + + you will then see + + */ + + namespace xml = "http://www.w3.org/XML/1998/namespace"; + + // alternative + //var xml:Namespace = new Namespace("xml", "http://www.w3.org/XML/1998/namespace"); + //descriptor.addNamespace(xml); + //trace( descriptor.toXMLString() ); + + app_name = descriptor.name.text.(@xml::lang == lang); + + // fall back to "en" if the app name for the default language is not found + if( (app_name == "") && (lang != "en") ) + { + //app_name = descriptor.name.text.(@w3xml::lang == "en"); + app_name = descriptor.name.text.(@xml::lang == "en"); + } + } + else + { + /* Note: + simple case without multiple languages + eg. + My App + */ + app_name = descriptor.name; + } + + //id + app_id = descriptor.id; + + //version + if( useVersionLabel ) + { + // by default use the version label if allowed + app_version = descriptor.versionLabel; + } + + if( app_version == "" ) + { + // fall back to the version number + app_version = descriptor.versionNumber; + } + } + + if( app_name != "" ) + { + appinfo.name = app_name; + } + + if( app_id != "" ) + { + appinfo.ID = app_id; + } + + if( app_version != "" ) + { + appinfo.version = app_version; + } + + if( installerID != "" ) + { + appinfo.installerID = installerID; + } + + return appinfo; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/generateAIRSystemInfo.as b/src/libraries/uanalytics/utils/generateAIRSystemInfo.as new file mode 100644 index 0000000..51fbdd6 --- /dev/null +++ b/src/libraries/uanalytics/utils/generateAIRSystemInfo.as @@ -0,0 +1,61 @@ +package libraries.uanalytics.utils +{ + import flash.display.DisplayObject; + + import libraries.uanalytics.tracker.SystemInfo; + + /** + * Utility function to gather automatically the SystemInfo + * from an AIR application. + * + *

+ * Almost the same function as generateFlashSystemInfo() + * but provide a bit more details for "screenColors" and "userLanguage". + *

+ * + * @param display DisplayObject reference (optional). + * + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + public function generateAIRSystemInfo( display:DisplayObject = null ):SystemInfo + { + var sysinfo:SystemInfo = new SystemInfo(); + sysinfo.screenResolution = getScreenResolution(); + + if( display && display.stage ) + { + sysinfo.viewportSize = getViewportSize( display.stage ); + } + + sysinfo.documentEncoding = getDocumentEncoding(); + + var screenColors:String = ""; + + if( display && display.stage ) + { + screenColors = getAIRScreenColors( display.stage ); + } + + if( screenColors == "" ) + { + screenColors = getScreenColors(); + } + + var userLanguage:String = ""; + + userLanguage = getAIRUserLanguage(); + + if( userLanguage == "" ) + { + userLanguage = getUserLanguage(); + } + + sysinfo.screenColors = screenColors; + sysinfo.userLanguage = userLanguage; + sysinfo.javaEnabled = false; + sysinfo.flashVersion = getFlashVersion(); + + return sysinfo; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/generateCLISystemInfo.as b/src/libraries/uanalytics/utils/generateCLISystemInfo.as new file mode 100644 index 0000000..393df0d --- /dev/null +++ b/src/libraries/uanalytics/utils/generateCLISystemInfo.as @@ -0,0 +1,157 @@ +package libraries.uanalytics.utils +{ + import shell.Runtime; + + import C.stdlib.*; + import C.unistd.which; + + import libraries.uanalytics.tracker.SystemInfo; + + /** + * Utility function to gather automatically the SystemInfo + * from a command-line program. + * + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html POSIX Locale + * @see http://docs.redtamarin.com/latest/shell/Runtime.html#api Runtime.api + */ + public function generateCLISystemInfo():SystemInfo + { + var sysinfo:SystemInfo = new SystemInfo(); + + /* Note: + for the command-line we ignore + screenResolution and viewportSize. + */ + + var documentEncoding:String = ""; + var userLanguage:String = ""; + + /* Note: + under POSIX, you could find somethign like + LANG = en_GB.UTF-8 + which allow us to find out the language and encoding used + */ + var LANG:String = getenv( "LANG" ); + if( LANG != "" ) + { + var pos:int = LANG.indexOf( "." ); + if( pos > -1 ) + { + userLanguage = LANG.substring( 0, pos ); + documentEncoding = LANG.substr( pos + 1 ); + } + else + { + userLanguage = LANG; + } + + userLanguage = userLanguage.split( "_" ).join( "-" ); + userLanguage = userLanguage.toLowerCase(); + } + + var JAVA:String = which( "java" ); + + if( documentEncoding != "" ) + { + sysinfo.documentEncoding = documentEncoding; + } + + // as default we set for a "gray" color depth + sysinfo.screenColors = "2-bits"; + + if( userLanguage != "" ) + { + sysinfo.userLanguage = userLanguage; + } + + if( JAVA != "" ) + { + sysinfo.javaEnabled = true; + } + + var FLASH:String = ""; + switch( Runtime.api ) + { + + case "FP_10_0": + case "AIR_1_5": + FLASH = "10 0"; + break; + + case "FP_10_0_32": + case "AIR_1_5_1": + FLASH = "10 0 r32"; + break; + + case "FP_10_1": + case "AIR_1_5_2": + FLASH = "10 1"; + break; + + case "AIR_2_0": + case "AIR_2_5": + FLASH = "10 2"; + break; + + case "FP_10_2": + case "AIR_2_6": + FLASH = "10 2"; + break; + + case "SWF_12": + case "AIR_2_7": + FLASH = "10 3"; + break; + + case "SWF_13": + case "AIR_3_0": + FLASH = "11 0"; + break; + + case "SWF_14": + case "AIR_3_1": + FLASH = "11 1"; + break; + + case "SWF_15": + case "AIR_3_2": + FLASH = "11 2"; + break; + + case "SWF_16": + case "AIR_3_3": + FLASH = "11 3"; + break; + + case "SWF_17": + case "AIR_3_4": + FLASH = "11 4"; + break; + + case "SWF_18": + case "AIR_3_5": + FLASH = "11 5"; + break; + + case "SWF_19": + case "AIR_3_6": + FLASH = "11 6"; + break; + + case "FP_9_0": + case "AIR_1_0": + default: + FLASH = "9 0"; + } + + if( FLASH != "" ) + { + sysinfo.flashVersion = FLASH; + } + + return sysinfo; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/generateCLIUUID.as b/src/libraries/uanalytics/utils/generateCLIUUID.as new file mode 100644 index 0000000..e841d57 --- /dev/null +++ b/src/libraries/uanalytics/utils/generateCLIUUID.as @@ -0,0 +1,69 @@ +package libraries.uanalytics.utils +{ + import flash.utils.ByteArray; + import crypto.generateRandomBytes; + + /** + * Generates a version 4 UUID string representation. + * + *

+ * format is xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx + * where x is any hexadecimal digit + * and y is oen of 8, 9, A, or B. + *

+ * + *

+ * Note: this is a temporary solution as the current redtamarin runtimes + * does not implment flash.crypto.generateRandomBytes yet.
+ * It will work under Linux and Mac OS X but not under Windows. + *

+ * + * @return a UUID v4 string. + * + * @playerversion AVM 0.4 + * @playerversion POSIX + + * @langversion 3.0 + * + * @see http://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29 Universally unique identifier v4 + */ + public function generateCLIUUID():String + { + var randomBytes:ByteArray = generateRandomBytes( 16 ); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + + var toHex:Function = function( n:uint ):String + { + var h:String = n.toString( 16 ); + h = (h.length > 1 ) ? h: "0"+h; + return h; + } + + var str:String = ""; + var i:uint; + var l:uint = randomBytes.length; + randomBytes.position = 0; + var byte:uint; + + for( i=0; i + * Will generate a string looking like: + *

+ * + * //windows + * uanalytics/0.8 (Windows NT 6.1) redtamarin/0.4.1T174 AVM+/2.1 cyclone + * + * //macintosh + * uanalytics/0.8 (Macintosh; Intel Mac OS X 10_10) redtamarin/0.4.1T174 AVM+/2.1 cyclone + * + * //linux + * uanalytics/0.8 (Linux x86_64) redtamarin/0.4.1T174 AVM+/2.1 cyclone + * uanalytics/0.8 (Linux i686) redtamarin/0.4.1T174 AVM+/2.1 cyclone + * + * + *

+ * In your analytics reports you will find: + *

+ *
    + *
  • + * Operating System: Windows
    + * Operating System Version: 7 + *
  • + *
  • + * Operating System: Macintosh
    + * Operating System Version: Intel 10.10 + *
  • + *
  • + * Operating System: Linux
    + * Operating System Version: i686 + *
  • + *
  • + * Operating System: Linux
    + * Operating System Version: x86_64 + *
  • + *
+ * + *

+ * Note:
+ * The documentation do warn about that + *

+ * + * Note that Google has libraries to identify real user agents. + * Hand crafting your own agent could break at any time. + * + *

+ * a wrong user-agent could be the reason of a hit request not being + * registered by the Google Analytics Servers. + *

+ * + * @param platform override the platform with either + * "windows", "macintosh", "linux" + * + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ua User Agent Override + */ + public function generateCLIUserAgent( platform:String = "" ):String + { + var ua:String = ""; + ua += "uanalytics/0.8"; + ua += " "; + ua += "("; + + if( platform == "" ) + { + platform = Runtime.platform; + } + else + { + switch( platform ) + { + case "windows": + case "macintosh": + case "linux": + //those are valid do nothing + break; + + default: + //not valid so we use system default + platform = Runtime.platform; + } + } + + + var p:String = platform.substr( 0, 1 ); + p = p.toUpperCase(); + platform = p + platform.substring( 1 ); + + /* Note: + the current release of Redtamarin do not detect further + than the operating system platform + eg. "windows", "macintosh", or "linux" + + It will be updated in the futur to detect the exact + operating system informations. + + As a workaround it can also be done manually + by parsing the result of + lsb_release -a + cat /etc/*release + uname -a + cat /System/Library/CoreServices/SystemVersion.plist + sysctl kern.osrelease + sysctl kern.osversion + */ + switch( platform ) + { + case "Windows": + /* Note: + "Windows" alone will not work + "Windows NT" without a version will not work + "Windows NT x.y" will work + */ + ua += platform; + ua += " NT 6.1"; + break; + + case "Macintosh": + /* Note: + "Macintosh" alone will not work + "Macintosh; Intel Mac OS X" without a version will not work + "Macintosh; Intel Mac OS X x_y" will work + */ + ua += platform; + ua += "; Intel Mac OS X 10_10"; + break; + + case "Linux": + ua += platform; + + /* Note: + "Linux" alone will not work + either "Linux x86_64" or "Linux i686" will work + */ + if( Runtime.is64bit() ) + { + ua += " x86_64"; + } + else + { + ua += " i686"; + } + break; + } + + ua += ")"; + ua += " "; + ua += "redtamarin/" + Runtime.redtamarin; + ua += " "; + ua += "AVM+/" + Runtime.version; + + return ua; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/generateFlashSystemInfo.as b/src/libraries/uanalytics/utils/generateFlashSystemInfo.as new file mode 100644 index 0000000..95fe316 --- /dev/null +++ b/src/libraries/uanalytics/utils/generateFlashSystemInfo.as @@ -0,0 +1,69 @@ +package libraries.uanalytics.utils +{ + import flash.display.DisplayObject; + + import libraries.uanalytics.tracker.SystemInfo; + + /** + * Utility function to gather automatically the SystemInfo + * from a SWF file. + * + *

+ * Even if the display argument is optional, + * it is the only way to obtain a reference to the stage property + * to obtain the "viewport size". + *

+ *
+     * If a display object is not added to the display list,
+     * its stage property is set to null.
+     * 
+ * + * @param display DisplayObject reference (optional). + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://help.adobe.com/en_US/as3/dev/WS5b3ccc516d4fbf351e63e3d118a9b90204-7e3e.html Basics of display programming + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/DisplayObject.html#stage DisplayObject.stage property + * + */ + public function generateFlashSystemInfo( display:DisplayObject = null ):SystemInfo + { + /* Note: + the principle is pretty simple, we build a defautl value object + if we can detect system info we then fill the info + otherwise we keep the value empty (or false, whatever the default). + + Yes we took a very conservative and safe approach to avoid as much + as possible errors, ex: "I don't understand why my display object + does not work", that said you can do more advanced things + like: use a JS bridge to detect if Java is enabled, find out the + document encoding, etc. + + The point is the default will work no matter what, more advanced users + will know how to setup things in order to gather more info. + + usage: + var tracker:DefaultTracker = new DefaultTracker( trackingId ); + var sysinfo:SystemInfo = generateFlashSystemInfo(); + tracker.add( sysinfo ); + + */ + var sysinfo:SystemInfo = new SystemInfo(); + sysinfo.screenResolution = getScreenResolution(); + + if( display && display.stage ) + { + sysinfo.viewportSize = getViewportSize( display.stage ); + } + + sysinfo.documentEncoding = getDocumentEncoding(); + sysinfo.screenColors = getScreenColors(); + sysinfo.userLanguage = getUserLanguage(); + sysinfo.javaEnabled = false; + sysinfo.flashVersion = getFlashVersion(); + + return sysinfo; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/generateUUID.as b/src/libraries/uanalytics/utils/generateUUID.as new file mode 100644 index 0000000..d0c14a4 --- /dev/null +++ b/src/libraries/uanalytics/utils/generateUUID.as @@ -0,0 +1,86 @@ +package libraries.uanalytics.utils +{ + import flash.crypto.generateRandomBytes; + import flash.utils.ByteArray; + + /** + * Generates a version 4 UUID string representation. + * + *

+ * format is xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx + * where x is any hexadecimal digit + * and y is oen of 8, 9, A, or B. + *

+ * + * @return a UUID v4 string. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29 Universally unique identifier v4 + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/crypto/package.html#generateRandomBytes() flash.crypto.generateRandomBytes() + */ + public function generateUUID():String + { + /* note: + using flash.crypto.generateRandomBytes + force a dependency on a minimum version of Flash Player 11.0 / AIR 3.0 + it can be refactored later to target lower versions + */ + + var randomBytes:ByteArray = generateRandomBytes( 16 ); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + + var toHex:Function = function( n:uint ):String + { + var h:String = n.toString( 16 ); + h = (h.length > 1 ) ? h: "0"+h; + return h; + } + + var str:String = ""; + var i:uint; + var l:uint = randomBytes.length; + randomBytes.position = 0; + var byte:uint; + + for( i=0; i "-" "-" "-" "-" + time_low = 4* + time_mid = 2* + time_high_and_version = 2* + variant_and_sequence = 2* + node = 6* + hexOctet = + + see http://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29 + Version 4 UUIDs have the form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx + where x is any hexadecimal digit and y is one of 8, 9, a, or b + (e.g., f47ac10b-58cc-4372-a567-0e02b2c3d479). + */ + var uuid:String = ""; + uuid += str.substr( 0, 8 ); + uuid += "-"; + uuid += str.substr( 8, 4 ); + uuid += "-"; + uuid += str.substr( 12, 4 ); + uuid += "-"; + uuid += str.substr( 16, 4 ); + uuid += "-"; + uuid += str.substr( 20, 12 ); + + return uuid; + } + +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/getAIRScreenColors.as b/src/libraries/uanalytics/utils/getAIRScreenColors.as new file mode 100644 index 0000000..e319bdb --- /dev/null +++ b/src/libraries/uanalytics/utils/getAIRScreenColors.as @@ -0,0 +1,27 @@ +package libraries.uanalytics.utils +{ + import flash.display.Screen; + import flash.display.Stage; + + /** + * Return the screen color depth. + * + * @param stage the stage property of a DisplayObject. + * + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/Screen.html#colorDepth Screen.colorDepth + */ + public function getAIRScreenColors( stage:Stage ):String + { + var current:Screen = getCurrentScreen( stage ); + + if( current ) + { + return current.colorDepth + "-bits"; + } + + return ""; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/getAIRUserLanguage.as b/src/libraries/uanalytics/utils/getAIRUserLanguage.as new file mode 100644 index 0000000..5eae56e --- /dev/null +++ b/src/libraries/uanalytics/utils/getAIRUserLanguage.as @@ -0,0 +1,31 @@ +package libraries.uanalytics.utils +{ + import flash.system.Capabilities; + + /** + * Returns a language tag (and script and region information, where applicable) + * defined by RFC4646. + * + *

+ * Note:
+ * The value of Capabilities.language property is limited to the possible + * values on this list. + * Because of this limitation, Adobe AIR applications should use the first + * element in the Capabilities.languages array to determine the primary user + * interface language for the system. + *

+ * + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/Capabilities.html#language Capabilities.language + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/Capabilities.html#languages Capabilities.languages + * @see http://www.ietf.org/rfc/rfc4646.txt RFC4646 + * @see http://help.adobe.com/en_US/as3/dev/WS9b644acd4ebe59993a5b57f812214f2074b-8000.html#WS9b644acd4ebe59993a5b57f812214f2074b-7ffd Choosing a locale + */ + public function getAIRUserLanguage():String + { + // more accurate than Capabilities.language + return Capabilities.languages[0]; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/getCLIHostname.as b/src/libraries/uanalytics/utils/getCLIHostname.as new file mode 100644 index 0000000..dfd93e7 --- /dev/null +++ b/src/libraries/uanalytics/utils/getCLIHostname.as @@ -0,0 +1,54 @@ +package libraries.uanalytics.utils +{ + import C.stdlib.*; + import C.unistd.*; + + /** + * Obtain the hostname of a command-line program. + * + * @playerversion AVM 0.4 + * @langversion 3.0 + * + * @see http://tools.ietf.org/html/rfc3875#section-4.1.14 SERVER_NAME + * @see http://docs.redtamarin.com/latest/C/unistd/package.html#gethostname() C.unistd.gethostname() + * @see http://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostname.html gethostname() + */ + public function getCLIHostname():String + { + var hostname:String = ""; + + /* Note: + when run with CGI you can access the SERVER_NAME env var + + for ex: + SERVER_NAME = as3lang.org + SERVER_NAME = www.as3lang.org + etc. + */ + var SERVER_NAME:String = getenv( "SERVER_NAME" ); + //trace( "SERVER_NAME = " + SERVER_NAME ); + + /* Note: + if SERVER_NAME is empty + that means the program is not running under CGI + you must be either a static binary, a shell script, or + an ABC/SWF file run with redshell + + then we can use gethostname() to resolve the machine hostname + */ + if( SERVER_NAME == "" ) + { + // get name of current host + hostname = gethostname(); + //trace( "gethostname() = " + hostname ); + } + + if( (hostname == null) || (hostname == "") ) + { + hostname = "localhost"; + //trace( "hostname = " + hostname ); + } + + return hostname; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/getCurrentScreen.as b/src/libraries/uanalytics/utils/getCurrentScreen.as new file mode 100644 index 0000000..89b9671 --- /dev/null +++ b/src/libraries/uanalytics/utils/getCurrentScreen.as @@ -0,0 +1,34 @@ +package libraries.uanalytics.utils +{ + import flash.display.Screen; + import flash.display.Stage; + + /** + * Utility function to obtain the current Screen + * of an AIR application. + * + *

+ * For example, if an AIR desktop application run on a system with + * multiple screens, this function will return the screen reference + * under which the app is running. + *

+ * + * @param stage the stage property of a DisplayObject. + * + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + public function getCurrentScreen( stage:Stage ):Screen + { + if( stage && stage.nativeWindow ) + { + var screens:Array = Screen.getScreensForRectangle( stage.nativeWindow.bounds ); + if( screens.length > 0 ) + { + return screens[0]; + } + } + + return Screen.mainScreen; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/getDocumentEncoding.as b/src/libraries/uanalytics/utils/getDocumentEncoding.as new file mode 100644 index 0000000..0ecd25f --- /dev/null +++ b/src/libraries/uanalytics/utils/getDocumentEncoding.as @@ -0,0 +1,42 @@ +package libraries.uanalytics.utils +{ + import flash.system.System; + + /** + * Return the encoding or code page of the current SWF file + * or AIR application. + * + *

+ * Because System.useCodePage = false by default, + * it should always be "UTF-8" (eg. we don't use the sytem code page + * so we know we use Unicode). + * And in the case where System.useCodePage = true + * then it will return the empty string (eg. the system code page is used + * so we don't know). + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/System.html#useCodePage System.useCodePage + */ + public function getDocumentEncoding():String + { + /* Note: + "as is" we have no way to detect the codepage + inside Flash/AIR, so if useCodePage is true + then we agre to "don't know" + */ + if( System.useCodePage ) + { + return ""; + } + + /* Note: + if useCodePage is false then we know + we use the default UTF-8 codepage + */ + return "UTF-8"; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/getFlashVersion.as b/src/libraries/uanalytics/utils/getFlashVersion.as new file mode 100644 index 0000000..de990b4 --- /dev/null +++ b/src/libraries/uanalytics/utils/getFlashVersion.as @@ -0,0 +1,32 @@ +package libraries.uanalytics.utils +{ + import flash.system.Capabilities; + + /** + * Return the Flash version. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/Capabilities.html#version Capabilities.version + */ + public function getFlashVersion():String + { + // AND 10,2,150,0 + var v:String = Capabilities.version; + + var tmp:Array = v.split( " " ); + // 10,2,150,0 + var v2:String = tmp[1]; + var c:Array = v2.split( "," ); + + var str:String = ""; + str += c[0]; + str += " " + c[1]; + str += " r" + c[2]; + // 10 2 r150 + + return str; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/getHostname.as b/src/libraries/uanalytics/utils/getHostname.as new file mode 100644 index 0000000..a5fed99 --- /dev/null +++ b/src/libraries/uanalytics/utils/getHostname.as @@ -0,0 +1,50 @@ +package libraries.uanalytics.utils +{ + import flash.net.LocalConnection; + + /** + * Obtain the hostname of the Flash or AIR application. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/LocalConnection.html#domain LocalConnection.domain + */ + public function getHostname():String + { + var hostname:String = ""; + + /* See doc: + In content running in the application security sandbox in Adobe AIR + (content installed with the AIR application), the runtime uses the + string app# followed by the application ID for the AIR application + (defined in the application descriptor file) in place of the superdomain. + For example a connectionName for an application with the + application ID com.example.air.MyApp connectionName + resolves to "app#com.example.air.MyApp:connectionName". + + In SWF files published for Flash Player 9 or later, the returned string + is the exact domain of the file, including subdomains. + For example, if the file is located at www.adobe.com, + this command returns "www.adobe.com". + + If the current file is a local file residing on the client computer + running in Flash Player, this command returns "localhost". + */ + if( LocalConnection.isSupported ) + { + var lc:LocalConnection = new LocalConnection(); + hostname = lc.domain; + } + + // we don not want to use: "app#com.example.air.MyApp:connectionName" + if( (hostname.substr( 0, 4) == "app#") || + (hostname == "") ) + { + hostname = "localhost"; + } + + return hostname; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/getScreenColors.as b/src/libraries/uanalytics/utils/getScreenColors.as new file mode 100644 index 0000000..0bb75f4 --- /dev/null +++ b/src/libraries/uanalytics/utils/getScreenColors.as @@ -0,0 +1,37 @@ +package libraries.uanalytics.utils +{ + import flash.system.Capabilities; + + /** + * Return the screen color depth. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/Capabilities.html#screenColor Capabilities.screenColor + */ + public function getScreenColors():String + { + var str:String = ""; + + switch( Capabilities.screenColor ) + { + case "bw": + str = "1"; + break; + + case "gray": + str = "2"; + break; + + case "color": + default: + str = "24"; + } + + str += "-bits"; + + return str; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/getScreenResolution.as b/src/libraries/uanalytics/utils/getScreenResolution.as new file mode 100644 index 0000000..4005626 --- /dev/null +++ b/src/libraries/uanalytics/utils/getScreenResolution.as @@ -0,0 +1,22 @@ +package libraries.uanalytics.utils +{ + import flash.system.Capabilities; + + /** + * Return the screen resolution. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/Capabilities.html#screenResolutionX Capabilities.screenResolutionX + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/Capabilities.html#screenResolutionY Capabilities.screenResolutionY + */ + public function getScreenResolution():String + { + var w:Number = Capabilities.screenResolutionX; + var h:Number = Capabilities.screenResolutionY; + + return String(w) + "x" + String(h); + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/getUserLanguage.as b/src/libraries/uanalytics/utils/getUserLanguage.as new file mode 100644 index 0000000..1aad782 --- /dev/null +++ b/src/libraries/uanalytics/utils/getUserLanguage.as @@ -0,0 +1,40 @@ +package libraries.uanalytics.utils +{ + import flash.system.Capabilities; + + /** + * Returns the language code of the system on which the content is running. + * + *

+ * The language is specified as a lowercase two-letter language code + * from ISO 639-1. + *

+ * + *

+ * On English systems, this property returns only the language code (en), + * not the country code. + * On Microsoft Windows systems, this property returns the user interface (UI) + * language, which refers to the language used for all menus, dialog boxes, + * error messages, and help files. + *

+ * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/Capabilities.html#language Capabilities.language + * @see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes List of ISO 639-1 codes + */ + public function getUserLanguage():String + { + var lang:String = Capabilities.language; + + // Other/unknown + if( lang == "xu" ) + { + lang = ""; + } + + return lang; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/getViewportSize.as b/src/libraries/uanalytics/utils/getViewportSize.as new file mode 100644 index 0000000..806605c --- /dev/null +++ b/src/libraries/uanalytics/utils/getViewportSize.as @@ -0,0 +1,24 @@ +package libraries.uanalytics.utils +{ + import flash.display.Stage; + + /** + * Utility function to return the "viewport size" + * (eg. the actual rectangle area used by the application). + * + * @param stage the stage property of a DisplayObject. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + * + * @see http://www.adobe.com/devnet/air/articles/multiple-screen-sizes.html Supporting the multiple screen sizes of multiple devices in Adobe AIR + */ + public function getViewportSize( stage:Stage ):String + { + var w:Number = stage.stageWidth; + var h:Number = stage.stageHeight; + + return String(w) + "x" + String(h); + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/isAIR.as b/src/libraries/uanalytics/utils/isAIR.as new file mode 100644 index 0000000..69a4ea9 --- /dev/null +++ b/src/libraries/uanalytics/utils/isAIR.as @@ -0,0 +1,16 @@ +package libraries.uanalytics.utils +{ + import flash.system.Security; + + /** + * Returns true if the current context is an AIR application. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @langversion 3.0 + */ + public function isAIR():Boolean + { + return Security.sandboxType == Security.APPLICATION; + } +} \ No newline at end of file diff --git a/src/libraries/uanalytics/utils/isDigit.as b/src/libraries/uanalytics/utils/isDigit.as new file mode 100644 index 0000000..0649232 --- /dev/null +++ b/src/libraries/uanalytics/utils/isDigit.as @@ -0,0 +1,27 @@ +package libraries.uanalytics.utils +{ + + /** + * Indicates if the specified character is a digit. + * + * @param c The expression to evaluate. + * @param index The optional index to evaluate a specific character in the + * passed-in expression. + * + * @return True if the specified character is a digit. + * + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @playerversion AVM 0.4 + * @langversion 3.0 + */ + public function isDigit( c:String , index:uint = 0 ):Boolean + { + if( index > 0 ) + { + c = c.charAt( index ) ; + } + + return ("0" <= c) && (c <= "9"); + } +} \ No newline at end of file diff --git a/src/uanalytics.as b/src/uanalytics.as new file mode 100644 index 0000000..4af70e6 --- /dev/null +++ b/src/uanalytics.as @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include "C/unistd/which.as"; +include "crypto/generateRandomBytes.as"; + +include "libraries/uanalytics/tracking/AnalyticsTracker.as"; +include "libraries/uanalytics/tracking/AnalyticsSender.as"; +include "libraries/uanalytics/tracking/Configuration.as"; +include "libraries/uanalytics/tracking/HitModel.as"; +include "libraries/uanalytics/tracking/HitSampler.as"; +include "libraries/uanalytics/tracking/HitSender.as"; +include "libraries/uanalytics/tracking/Metadata.as"; +include "libraries/uanalytics/tracking/RateLimiter.as"; +include "libraries/uanalytics/tracking/RateLimitError.as"; +include "libraries/uanalytics/tracking/Tracker.as"; + +include "libraries/uanalytics/tracker/HitType.as"; +include "libraries/uanalytics/tracker/DataSource.as"; +include "libraries/uanalytics/tracker/SessionControl.as"; +include "libraries/uanalytics/tracker/ApplicationInfo.as"; +include "libraries/uanalytics/tracker/SystemInfo.as"; +include "libraries/uanalytics/tracker/TimingInfo.as"; +include "libraries/uanalytics/tracker/CommandLineTracker.as"; +include "libraries/uanalytics/tracker/CliTracker.as"; + +include "libraries/uanalytics/tracker/senders/TraceHitSender.as"; +include "libraries/uanalytics/tracker/senders/BSDSocketHitSender.as"; +include "libraries/uanalytics/tracker/senders/CurlHitSender.as"; + +include "libraries/uanalytics/utils/isDigit.as"; +include "libraries/uanalytics/utils/crc32.as"; +include "libraries/uanalytics/utils/getCLIHostname.as"; +include "libraries/uanalytics/utils/generateCLIUUID.as"; +include "libraries/uanalytics/utils/generateCLISystemInfo.as"; +include "libraries/uanalytics/utils/generateCLIUserAgent.as"; + +include "libraries/uanalytics/tracker/addons/DebugFileSystemStorage.as"; + +"uanalytics 0.8.0"; \ No newline at end of file diff --git a/src/uanalytics.png b/src/uanalytics.png new file mode 100644 index 0000000..1cd5fad Binary files /dev/null and b/src/uanalytics.png differ diff --git a/src/version.properties b/src/version.properties new file mode 100644 index 0000000..b93a4f6 --- /dev/null +++ b/src/version.properties @@ -0,0 +1,4 @@ + +version.major=0 +version.minor=8 +version.build=0