Skip to content

Instantly share code, notes, and snippets.

@smhmic
Last active February 13, 2020 23:06
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save smhmic/492213ea74bb47c5878b to your computer and use it in GitHub Desktop.
Save smhmic/492213ea74bb47c5878b to your computer and use it in GitHub Desktop.
GTM listener for non-GTM-based GA tracking (hardcoded on-page, plugins, etc) [a.k.a. spy/hijack GA]
//
// These examples are for UA, but would be very similar for GA Async.
//
(function spyGoogleAnalytics( callback ){
/* GA library -specific code goes here. (See other gist files for full code.) */
})(function( a ){
// v v v CUSTOM CODE GOES HERE v v v
// RETURN FALSE to prevent original hit from firing.
// By default, the original hit fires just as it normally would.
// ARGUMENTS passed to the GA object are available in the array `a`.
// See GA documentation for parameter formats: https://goo.gl/muCY7Q
// FOR DEBUGGING: console.debug.apply( console, a );
var dataLayer = (window.dataLayer = window.dataLayer || []);
//
// EXAMPLE 1: Expose everything (pageviews, events, tracker config, etc) to GTM.
//
var frame = {},
namespace = 'spy.ga.' + a[0]
+ (typeof a[1] == 'string' ? '.'+a[1]
+ (typeof a[2] == 'string' ? '.'+a[2] : '') : '');
frame.event = namespace;
frame[namespace] = a;
dataLayer.push( frame );
// return true; // Allow non-GTM hits to fire unmodified.
// return false; // Prevent all non-GTM hits from firing.
//
// EXAMPLE 2: Expose every event to GTM (simple).
//
if( a[0] == "send" && a[1] == "event" ){
dataLayer.push({
'event' : 'spy.ga.event.' + a[2],
'spy.ga.event' : {
'category' : a[2],
'action' : a[3],
'label' : a[4],
'value' : a[5],
'nonInteraction' : a[6] && a[6].nonInteraction
}
});
}
//
// EXAMPLE 3: Expose every event to GTM. Unlike Example 2 above, this
// handles alternate parameter formats (see https://goo.gl/muCY7Q).
//
var fields;
if( a[0] == "send" ){
// If last argument is object, use as fields object.
fields = typeof a[a.length-1] == 'object' ? a.pop() : {};
if( ( a[1] || fields.hitType ) == "event" ){
fields.category = fields.eventCategory || a[2];
fields.action = fields.eventAction || a[3];
fields.label = fields.eventLabel || a[4];
fields.value = fields.eventValue || a[5];
fields.nonInteraction = fields.nonInteraction || undefined;
//TODO: If using fields other than the standard event fields above,
// either: a) use same code as `nonInteraction` above to explicitly
// set new value, OR b) clear all old values before pushing new ones
// with `google_tag_manager[{{Container
// ID}}].dataLayer.set('ga.event',undefined);`
dataLayer.push({
'event' : 'spy.ga.event.' + fields.eventCategory,
'spy.ga.event' : fields
});
}
}
//
// EXAMPLE 4: Intercept built-in tracking from ShareThis and AddThis
// so custom naming conventions can be applied via GTM.
//
if( a[0] == "send" && a[1] == "event" && ( a[2] == "ShareThis" || a[2] == "addthis" ) ){
dataLayer.push({
'event' : 'socialShare via' + a[2],
'socialShare' : {
'network' : a[3],
'url' : a[4]
}
});
return false; // Return false to stop original hit from attempting to fire.
// Returning true allows analytics.js to try to fire ShareThis/Addthis
// built-in tracking. Only return true if either on-page tracking
// OR AddThis/Sharethis is configured to track to a separate
// GA Property than the Property tracked by GTM.
}
// ^ ^ ^ CUSTOM CODE END ^ ^ ^
});
/**
* Spy on asynchronous calls to Google's Traditional Analytics (ga.js) library.
*
* Built with GTM in mind, but can be used for other TMSes or custom JS.
*
* Intended to piggyback and/or block external (non-GTM-based) tracking (i.e.
* on-page code, plugins/platforms with built-in tracking, etc), in order to:
* - leverage benefits of GTM for non-GTM-based tracking
* - override naming system of external tracking with custom conventions
* - integrate external tracking with your measurement implementation
*
* NOTE: NOT THOROUGHLY TESTED.
*
* NOTE: This is as reliable as any custom code, including dataLayer snippets.
* Performance impact is minimal. But it's preferable to migrate/modify/remove
* the external tracking, for a cleaner implementation. This code is provided
* for cases where editing the external code is not an option.
*
* NOTE: In order to spy on commands that fire immediately / page load/ready,
* this must run before the code that loads ga.js.
*
* @see https://gist.github.com/smhmic/492213ea74bb47c5878b
* @author Stephen M Harris <smhmic@gmail.com>
* @version 0.0.1
*/
(function spyGoogleAnalytics( callback ){
var
gaqObjName = '_gaq',
_gaq = window[gaqObjName],
console = window.console || {error:function(){}},
k, i,
// Processes each set of arguments sent to _gaq.push.
// If returns false, arguments will not be passed through to _gaq.push.
handler = function( a ){ try{
// If command is not via GTM Tag, return result of custom code.
return ( a[0] && a[0][0] && a[0][0].substr && a[0][0].substr( 0, 4 ) == 'gtm.' )
|| ( false !== callback( a ) );
}catch(ex){ console.error(ex) } },
// Process array of push'ed args, and return filtered array.
processArgSet = function( arr ){
var aFiltered = [];
for( i=0; i< arr.length; i++ )
if( handler( arr[i] ) )
aFiltered.push( arr[i] );
return aFiltered;
},
// Spy on the _gaq.push function.
spy = function(){
var gaqPushOrig = window[gaqObjName].push;
// Replace _gaq.push with a proxy.
window[gaqObjName].push = function(){
var aFiltered = processArgSet( [].slice.call( arguments ) );
// If some args passing through, or no args were passed,
// pass through to _gaq.push.
if( !arguments.length || aFiltered.length )
return gaqPushOrig.apply( window[gaqObjName], aFiltered );
};
// Ensure methods/members of _gaq.push remain accessible on the proxy.
for( k in gaqPushOrig )
if( gaqPushOrig.hasOwnProperty( k ) )
window[gaqObjName].push[k] = gaqPushOrig[k];
};
if( ! _gaq ){
// Instantiate GA command queue a la GA Async snippet.
_gaq = window[gaqObjName] = [];
}
if( window._gat ){ // GA already loaded; cannot see previous commands.
spy();
} else if( _gaq.slice ){ // GA snippet ran, but GA not loaded.
// Filter all existing command queue items through custom code.
_gaq = window[gaqObjName] = processArgSet( _gaq );
_gaq.push( spy );
spy();
} else {
throw new Error('spyGoogleAnalytics aborting; '
+'`'+gaqObjName+'` not the GA object.' );
}
})(function( a ){
/** @var [Array] a - The arguments pushed onto `_gaq` **/
// v v v CUSTOM CODE GOES HERE v v v
// RETURN FALSE to prevent original hit from firing.
// By default, the original hit fires just as it normally would.
// ARGUMENTS passed to the GA object are available in the array `a`.
// See GA documentation for parameter formats: https://goo.gl/muCY7Q
// FOR DEBUGGING: console.debug.apply( console, a );
// EXAMPLES: https://git.io/vK4VJ
// ^ ^ ^ CUSTOM CODE END ^ ^ ^
});
/**
* Spy on calls to Google's Universal Analytics (analytics.js) library.
*
* Built with GTM in mind, but can be used for other TMSes or custom JS.
*
* Intended to piggyback and/or block external (non-GTM-based) tracking (i.e.
* on-page code, plugins/platforms with built-in tracking, etc), in order to:
* - leverage benefits of GTM for non-GTM-based tracking
* - override naming system of external tracking with custom conventions
* - integrate external tracking with your measurement implementation
*
* NOTE: This is as reliable as any custom code, including dataLayer snippets.
* Performance impact is minimal. But it's preferable to migrate/modify/remove
* the external tracking, for a cleaner implementation. This code is provided
* for cases where editing the external code is not an option.
*
* NOTE: In order to spy on commands that fire immediately / page load/ready,
* this must run before the code that loads analytics.js.
*
* @see https://gist.github.com/smhmic/492213ea74bb47c5878b
* @author Stephen M Harris <smhmic@gmail.com>
* @version 0.4
*/
(function spyGoogleAnalytics( callback ){
var
gaObjName = window.GoogleAnalyticsObject || 'ga',
ga = window[gaObjName],
console = window.console || {error:function(){}},
k, q, i,
// Processes each set of arguments sent to GA object.
// If returns false, arguments will not be passed through to GA object.
handler = function( args ){ try{
// If command is not via GTM Tag, return result of custom code.
return ( args[0] && args[0].substr && args[0].substr( 0, 4 ) == 'gtm.' )
|| ( false !== callback( args ) );
}catch(ex){ console.error(ex) } },
// Spy on the GA object.
spy = function(){
var a, gaOrig = window[gaObjName];
// Replace GA object with a proxy.
window[gaObjName] = function(){
if( handler( a = [].slice.call( arguments ) ) )
// If command is via GTM Tag or custom code does not return false,
// pass through to GA command queue.
return gaOrig.apply( gaOrig, a );
};
// Ensure methods/members of GA object remain accessible on the proxy.
for( k in gaOrig )
if( gaOrig.hasOwnProperty( k ) )
window[gaObjName][k] = gaOrig[k];
};
if( ! ga ){
// Instantiate GA command queue a la UA snippet.
ga = window[gaObjName] = function(){
(window[gaObjName].q=window[gaObjName].q||[]).push( arguments ); };
ga.l = 1 * new Date();
}
if( ga.getAll ){ // GA already loaded; cannot see previous commands.
spy();
} else if( ga.l ){ // UA snippet ran, but GA not loaded.
if( ga.q ){
// Run all existing command queue items through custom code.
for( q=[], i = 0; i < ga.q.length; i++ )
if( handler( [].slice.call( ga.q[i] ) ) )
q.push( ga.q[i] );
ga.q = q;
} else {
// No commands queued yet, instantiate queue.
ga.q = [];
}
ga( spy );
spy();
} else {
throw new Error('spyGoogleAnalytics aborting; '
+'`'+gaObjName+'` not the GA object.' );
}
})(function( a ){
/** @var [Array] a - arguments passed to `ga()` **/
// v v v CUSTOM CODE GOES HERE v v v
// RETURN FALSE to prevent original hit from firing.
// By default, the original hit fires just as it normally would.
// ARGUMENTS passed to the GA object are available in the array `a`.
// See GA documentation for parameter formats: https://goo.gl/muCY7Q
// FOR DEBUGGING: console.debug.apply( console, a );
// EXAMPLES: https://git.io/vK4VJ
// ^ ^ ^ CUSTOM CODE END ^ ^ ^
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment