Last active
February 13, 2020 23:06
-
-
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]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 ^ ^ ^ | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 <[email protected]> | |
* @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 ^ ^ ^ | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 <[email protected]> | |
* @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