Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
This is for a site on UA, that needs to track sessions across some digital assets/platforms that are stuck on legacy versions of GA. This only works for traffic clicking from the UA site to the asset. (But after clicking from UA > Asset once, all subsequent Asset > UA clicks do not need cross-domain tracking and will not break the session).
<script>
/**
* This script facilitates cross-domain tracking when one site is runs
* GA Universal and another runs legacy GA (Traditional or Async/Classic).
*
* This integration currently unilateral, working only for users
* navigating from Universal > Legacy. For example: if a user clicks
* on a paid ad and lands directly on your asset/platform (legacy),
* then goes to your main site (UA), then clicks back to your asset/platform
* and converts, it will be tracked as two users: one that bounced from a
* paid ad, and one with a conversion attributed to whatever channel last
* brought them to the site (or if they're a new user, a self-referral or
* direct, depending on your Referral Exclusion List settings).
* THE MORAL: Do not use this if you get traffic directly to your asset/platform!
*
* To implement:
* 1) Ensure GA Traditional/Async tracking allows linker (@see http://j.mp/1Y9h4QH).
* 2) Place this script on site(s) running Universal (If using GTM, place in
* Custom HTML Tag (wrapped in `script` tags), firing on 'All Pages').
* 3) Update `GA_LEGACY_DOMAIN_HERE` to match URLs of
* site(s)/page(s) running legacy GA.
*/
(function decorateLinksForAsyncGA(){
// Only links matching this pattern will be decorated.
// v v v v v v UPDATE THIS REGEX v v v v v v
var traditionalUrlRegex = /^https?:\/\/(GA_LEGACY_DOMAIN_HERE)([\/?#]|$)/i;
// ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
var
gaObjName = window.GoogleAnalyticsObject || 'ga',
ga = window[gaObjName],
linkerParams;
// Delay execution if ...
if( ! ga // GA command queue is not instantiated,
|| ! ga.getAll // GA is not loaded,
|| ! ga.getAll().length // or a tracker has been created.
) return window.setTimeout( decorateLinksForAsyncGA, 100 );
function makeLinkerParams(){
var
num = 1,
c = ga.getAll()[0].get( 'clientId' ).split('.'),
hash = function( a ){var b=1,c=0,d;if(a)for(b=0,d=a.length-1;
0<=d;d--)c=a.charCodeAt(d),b=(b<<6&268435455)+c+(c<<14),
c=b&266338304,b=0!=c?b^c>>21:b;return b},
domainHash = hash( location.host ),
params = [
/* utma */ [ domainHash, c[0], c[1], c[1], c[1], num ].join('.'),
/* utmb */ [ domainHash, 1, 10, c[1] ].join('.'),
/* utmc */ domainHash,
/* utmx */ '-',
/* utmz */ [ domainHash, c[1], num, num, 'utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)' ].join('.'),
/* utmv */ '-'
];
return '__utma=' + params[0]
+ '&__utmb=' + params[1]
+ '&__utmc=' + params[2]
+ '&__utmx=' + params[3]
+ '&__utmz=' + params[4]
+ '&__utmv=' + params[5]
+ '&__utmk=' + hash( params.join( '' ) );
}
function onclickDecorateLink(){
var el = ( arguments[0] || window.e ).target, p;
if( el.tagName !== 'A' ) return;
if( ! el.href.match( traditionalUrlRegex ) ) return;
if( ! linkerParams ) linkerParams = makeLinkerParams();
else if( el.href.indexOf( linkerParams ) ) return;
p = /([^#?]*)([^#]*)(.*)/.exec( el.href );
el.href = p[1] + p[2] + ( p[2] ? '&' : '?' ) + linkerParams + p[3];
}
if( document.addEventListener )
document.addEventListener( 'click', onclickDecorateLink, false );
else
document.attachEvent( 'onclick', onclickDecorateLink );
})();
</script>
(/** @callback - Runs repeatedly until a UA tracker is created. */
function decorateLinksForAsyncGA(){
var
/**
* Only links matching this pattern will be decorated.
* @const {RegExp} */
traditionalUrlRegex = /^https?:\/\/(GA_LEGACY_DOMAIN_HERE)([\/?#]|$)/i,
/**
* Variable name of the global Universal GA object.
* @var {Object} */
gaObjName = window.GoogleAnalyticsObject || 'ga',
/**
* The global Universal GA object.
* @var {Object} ga
* @prop {function} ga.getAll - Returns an array of tracker objects. */
ga = window[gaObjName],
/**
* GA legacy linker params; created on first matching link click.
* @var {string} */
linkerParams;
// Delay execution if ...
if( ! ga // GA command queue is not instantiated,
|| ! ga.getAll // GA is not loaded,
|| ! ga.getAll().length // or a tracker has been created.
) return window.setTimeout( decorateLinksForAsyncGA, 100 );
/**
* @function - Creates GA legacy linker parameters using UA client id.
* @returns {string} Linker url query parameters w/o leading "?" or "&".
*/
function makeLinkerParams(){
var
/**
* Integer used multiple places in GA legacy linker/cookie
* structure. Often 1, sometimes higher. Meaning/significance unknown.
* @const {number} */
num = 1,
/**
* Parts of the UA client id (cid). Yields two strings of
* digits (unless cid structure is customized). Second part used
* multiple places in GA legacy linker/cookie structure.
* @var {Array} */
c = ga.getAll()[0].get( 'clientId' ).split('.'),
/**
* Generate hash based on a string. Identical to the hash functions
* in ga.js and analytics.js.
* @function
* @param {string} The string to hash.
* @returns {string} The hash of the passed string. */
hash = function( a ){var b=1,c=0,d;if(a)for(b=0,d=a.length-1;
0<=d;d--)c=a.charCodeAt(d),b=(b<<6&268435455)+c+(c<<14),
c=b&266338304,b=0!=c?b^c>>21:b;return b},
/**
* Hash of current domain. Used multiple places in GA Classic
* linker/cookie structure.
* @var {string} */
domainHash = hash( location.host ),
/** @var {string[]} - GA Classic linker parameter values. */
params = [
/* utma */ [ domainHash, c[0], c[1], c[1], c[1], num ].join('.'),
/* utmb */ [ domainHash, 1, 10, c[1] ].join('.'),
/* utmc */ domainHash,
/* utmx */ '-',
/* utmz */ [ domainHash, c[1], num, num, 'utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)' ].join('.'),
/* utmv */ '-'
];
return '__utma=' + params[0]
+ '&__utmb=' + params[1]
+ '&__utmc=' + params[2]
+ '&__utmx=' + params[3]
+ '&__utmz=' + params[4]
+ '&__utmv=' + params[5]
+ '&__utmk=' + hash( params.join( '' ) );
}
/**
* Click handler; decorates clicked link with GA Traditional linker
* parameters (as defined in `makeLinkerParams()`) if the link url
* matches the `traditionalUrlRegex` pattern.
* @callback
*/
function onclickDecorateLink(){
/**
* @name e
* @global
* @type {Object}
* @prop {Object} e.target
*/
var /** @var {Object} - The clicked element. */
el = ( arguments[0] || window.e ).target,
/** @var {string[]} - Link url parts. */
p; // href parts
// Abort if not an html link.
if( el.tagName !== 'A' ) return;
// Abort if link doesn't match the configured URL pattern.
if( ! el.href.match( traditionalUrlRegex ) ) return;
// Generate linker parameters once on this page.
if( ! linkerParams ) linkerParams = makeLinkerParams();
// Abort if link URL was already decorated with the same parameters.
else if( el.href.indexOf( linkerParams ) ) return;
// Update link url with linker parameters.
p = /([^#?]*)([^#]*)(.*)/.exec( el.href );
el.href = p[1] + p[2] + ( p[2] ? '&' : '?' ) + linkerParams + p[3];
}
// Listen for all clicks.
if( document.addEventListener )
document.addEventListener( 'click', onclickDecorateLink, false );
else
document.attachEvent( 'onclick', onclickDecorateLink );
})();
// Run this in the console to check if the GA Legacy (Traditional or Async/Classic) tracking on the page allow the linker paramaters needed for cross-domain tracking.
(function(){
var t, v, trackers, _gat = window._gat;
if( ! _gat ) return console.error('Legacy GA not found on page.');
trackers = _gat._getTrackers();
console.log( trackers.length+' legacy trackers found.' );
for(var i=0; i<trackers.length; i++ ){
t = trackers[i]; v = t.get('x10');
// Sanity check, since this is an undocumented way to access data from ga.js.
if( t._setAllowLinker(!v), t.get('x10')===!v && (t._setAllowLinker(v)||1) )
console.log( t._getAccount()+' setAllowLinker: '+t.get('x10') );
else console.error('This code does not work for this version of ga.js');
};
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment