Skip to content

Instantly share code, notes, and snippets.

@Startouf
Last active October 22, 2022 08:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Startouf/39d1f37c0a0413a19cf9e0da6afc0cdf to your computer and use it in GitHub Desktop.
Save Startouf/39d1f37c0a0413a19cf9e0da6afc0cdf to your computer and use it in GitHub Desktop.
Modern UTM & Referral Cookie Replicator
<script id="gtm-modern-cookie-replicator">
/**
* Modern UTM & Referral Cookie Replicator
*
* Inspired from the UTMZ cookie replicator,
* whose goal was to makes a generally faithful representation
* of the old __utmz cookie from Classic Analytics. ;
*
* the "Modern" cookie replicator
* - stores and decodes the cookie as/from JSON format
* - stores raw UTM parameters along with google "friendly" equivalents using prefixes
* - stores the time at which utm of ga referral variables were updated
* - stores the first and last visited hosts
* - stores the first referrer
* - generates a unique visit ID
*
* Its goal is to be
* - data warehouse friendly (keep all raw data)
* - session friendly (you know exactly when the UTM were first set or updated)
*
* Stores the data in a cookie named cookieName (default `__cookie_utms`).
* Also sets a session cookie named sessionCookieName (default `__cookie_utms_ses`).
*
* Data is stored in the cookie_utms cookie in JSON format
* brackets indicate optional data:
*
* {
* ga_source: SOURCE
* ga_medium: MEDIUM
* ga_campaign: CAMPAIGN
* saved_at: CAMPAIGN
* first_visited_host: HOST
* last_visited_host: HOST
* referrer: STRING | null
* visit_id: STRING
* [|utm_campaign: UTM_CAMPAIGN]
* [|utm_medium: UTM_MEDIUM]
* [|utm_source: UTM_CAMPAIGN]
* [|utm_content: UTM_CONTENT]
* [|utm_term: UTM_TERM]
* [|utm_keyword: UTM_KEYWORD]
* }
*/
(function(document) {
var referrer = document.referrer;
var gaReferral = {
'ga_source': '(direct)',
'ga_medium': '(none)',
'ga_campaign': '(not set)'
};
var cookieName = '__cookie_utms'
var sessionCookieName = '__cookie_utms_ses'
var thisHostname = document.location.hostname;
var thisDomain = getDomain_(thisHostname);
var referringDomain = getDomain_(document.referrer);
var sessionCookie = getCookie_(sessionCookieName);
// Expiration 4 hours from UTM registration
var cookieExpiration = new Date(Date.now() + 4 * (60 * 60 * 1000) );
var qs = document.location.search.replace('?', '');
var hash = document.location.hash.replace('#', '');
var gaParams = parseGoogleParams(qs + '#' + hash);
var referringInfo = parseGaReferrer(referrer);
var storedVals = getCookie_(cookieName);
var referralOrUTMChanged = false
var newCookieVals = {};
var key;
// Always rewrite previous values
// (we need to extend the cookie expiration)
if (storedVals) {
for (key in storedVals) {
if (typeof storedVals[key] !== 'undefined') {
newCookieVals[key] = storedVals[key];
}
}
} else {
for (key in gaReferral) {
if (typeof gaReferral[key] !== 'undefined') {
newCookieVals[key] = gaReferral[key];
}
}
newCookieVals['visit_id'] = generateId()
newCookieVals['referrer'] = referrer
}
// Do not erase existing stuff when browsing same domain
if (sessionCookie && referringDomain === thisDomain) {
gaParams = null;
referringInfo = null;
}
if (gaParams && (gaParams.utm_source || gaParams.gclid || gaParams.dclid)) {
referralOrUTMChanged = true
// Override ga_ with actual UTM values
for (key in gaParams) {
if (typeof gaParams[key] !== 'undefined') {
gaReferral[key] = gaParams[key];
}
}
// Google stuff
if (gaParams.gclid || gaParams.dclid) {
gaReferral.ga_source = 'google';
gaReferral.ga_medium = gaReferral.ga_utmgclid ? 'cpc' : 'cpm';
}
} else if (referringInfo) {
referralOrUTMChanged = true
// Override ga_ with actual referring info values
gaReferral.ga_source = referringInfo.source;
gaReferral.ga_medium = referringInfo.medium;
if (referringInfo.term) {
gaReferral.ga_term = referringInfo.term
}
}
if (!storedVals || !storedVals.first_visited_host) {
newCookieVals.first_visited_host = thisHostname
}
newCookieVals.last_visited_host = thisHostname
if (referralOrUTMChanged) {
for (key in gaReferral) {
if (typeof gaReferral[key] !== 'undefined') {
newCookieVals[key] = gaReferral[key];
}
}
newCookieVals.saved_at = new Date().toISOString();
}
writeCookie_(cookieName, newCookieVals, cookieExpiration, '/', thisDomain);
writeCookie_(sessionCookieName, 1, null, '/', thisDomain);
function parseGoogleParams(str) {
var campaignParams = ['source', 'medium', 'campaign', 'term', 'content', 'keyword'];
var regex = new RegExp('(utm_(' + campaignParams.join('|') + ')|(d|g)clid)=.*?([^&#]*|$)', 'gi');
var gaParams = str.match(regex);
var paramsObj,
vals,
len,
i;
if (gaParams) {
paramsObj = {};
len = gaParams.length;
for (i = 0; i < len; i++) {
vals = gaParams[i].split('=');
if (vals) {
paramsObj[vals[0]] = vals[1];
}
}
}
return paramsObj;
}
function parseGaReferrer(referrer) {
if (!referrer) return;
var searchEngines = {
'daum.net': {
'p': 'q',
'n': 'daum'
},
'eniro.se': {
'p': 'search_word',
'n': 'eniro '
},
'naver.com': {
'p': 'query',
'n': 'naver '
},
'yahoo.com': {
'p': 'p',
'n': 'yahoo'
},
'msn.com': {
'p': 'q',
'n': 'msn'
},
'bing.com': {
'p': 'q',
'n': 'live'
},
'aol.com': {
'p': 'q',
'n': 'aol'
},
'lycos.com': {
'p': 'q',
'n': 'lycos'
},
'ask.com': {
'p': 'q',
'n': 'ask'
},
'altavista.com': {
'p': 'q',
'n': 'altavista'
},
'search.netscape.com': {
'p': 'query',
'n': 'netscape'
},
'cnn.com': {
'p': 'query',
'n': 'cnn'
},
'about.com': {
'p': 'terms',
'n': 'about'
},
'mamma.com': {
'p': 'query',
'n': 'mama'
},
'alltheweb.com': {
'p': 'q',
'n': 'alltheweb'
},
'voila.fr': {
'p': 'rdata',
'n': 'voila'
},
'search.virgilio.it': {
'p': 'qs',
'n': 'virgilio'
},
'baidu.com': {
'p': 'wd',
'n': 'baidu'
},
'alice.com': {
'p': 'qs',
'n': 'alice'
},
'yandex.com': {
'p': 'text',
'n': 'yandex'
},
'najdi.org.mk': {
'p': 'q',
'n': 'najdi'
},
'seznam.cz': {
'p': 'q',
'n': 'seznam'
},
'search.com': {
'p': 'q',
'n': 'search'
},
'wp.pl': {
'p': 'szukaj ',
'n': 'wirtulana polska'
},
'online.onetcenter.org': {
'p': 'qt',
'n': 'o*net'
},
'szukacz.pl': {
'p': 'q',
'n': 'szukacz'
},
'yam.com': {
'p': 'k',
'n': 'yam'
},
'pchome.com': {
'p': 'q',
'n': 'pchome'
},
'kvasir.no': {
'p': 'q',
'n': 'kvasir'
},
'sesam.no': {
'p': 'q',
'n': 'sesam'
},
'ozu.es': {
'p': 'q',
'n': 'ozu '
},
'terra.com': {
'p': 'query',
'n': 'terra'
},
'mynet.com': {
'p': 'q',
'n': 'mynet'
},
'ekolay.net': {
'p': 'q',
'n': 'ekolay'
},
'rambler.ru': {
'p': 'words',
'n': 'rambler'
},
'google': {
'p': 'q',
'n': 'google'
}
};
var a = document.createElement('a');
var values = {};
var searchEngine,
termRegex,
term;
a.href = referrer;
// Shim for the billion google search engines
if (a.hostname.indexOf('google') > -1) {
referringDomain = 'google';
}
if (searchEngines[referringDomain]) {
searchEngine = searchEngines[referringDomain];
termRegex = new RegExp(searchEngine.p + '=.*?([^&#]*|$)', 'gi');
term = a.search.match(termRegex);
values.source = searchEngine.n;
values.medium = 'organic';
values.term = (term ? term[0].split('=')[1] : '') || '(not provided)';
} else if (referringDomain !== thisDomain) {
values.source = a.hostname;
values.medium = 'referral';
}
return values;
}
function writeCookie_(name, value, expiration, path, domain) {
var str = name + '=' + JSON.stringify(value) + '; SameSite=Lax; Secure';
if (expiration) str += 'Expires=' + expiration.toGMTString() + ';';
if (path) str += 'Path=' + path + ';';
if (domain) str += 'Domain=' + domain + ';';
document.cookie = str;
}
function getCookie_(cname) {
var matchingCookie = document.cookie.split('; ').find(function (row) {
return row.startsWith(cname + '=');
})
if(!matchingCookie) { return null; }
var cookie = matchingCookie.split('=')[1]
if (cookie.startsWith('{')) {
return JSON.parse(cookie);
} else {
return null;
}
}
function getDomain_(url) {
if (!url) return;
var a = document.createElement('a');
a.href = url;
try {
return a.hostname.match(/[^.]*\.[^.]{2,3}(?:\.[^.]{2,3})?$/)[0];
} catch(squelch) {}
}
// https://stackoverflow.com/a/2117523/1177228
function generateId() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, function (c) {
return (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16);
});
}
})(document);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment