From fa095668a2b8cd392bb30b7d97bf0b801e201a09 Mon Sep 17 00:00:00 2001 From: Allan Carroll Date: Wed, 25 Feb 2015 21:52:58 -0800 Subject: [PATCH 1/2] Automatically gather UTM parameters --- amplitude.js | 44 ++++++++++++++++++++++++++- amplitude.min.js | 4 +-- src/amplitude.js | 38 +++++++++++++++++++++++- test/amplitude.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 4 deletions(-) diff --git a/amplitude.js b/amplitude.js index 78b5ffab..42395260 100644 --- a/amplitude.js +++ b/amplitude.js @@ -157,6 +157,7 @@ Amplitude.prototype._eventId = 0; Amplitude.prototype._sending = false; Amplitude.prototype._lastEventTime = null; Amplitude.prototype._sessionId = null; +Amplitude.prototype._newSession = false; /** * Initializes Amplitude. @@ -164,6 +165,8 @@ Amplitude.prototype._sessionId = null; * opt_userId An identifier for this user * opt_config Configuration options * - saveEvents (boolean) Whether to save events to local storage. Defaults to true. + * - utmParams (string) Optional utm data in query string format. + * Pulled from location.search otherwise. */ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config) { try { @@ -211,11 +214,16 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config) { this.sendEvents(); } + // Parse the utm properties out of cookies and query for adding to user properties. + var utmParams = opt_config && opt_config.utmParams || location.search; + this._utmProperties = Amplitude._getUtmData(Cookie.get('__utmz'), utmParams); + this._lastEventTime = parseInt(localStorage.getItem(LocalStorageKeys.LAST_EVENT_TIME)) || null; this._sessionId = parseInt(localStorage.getItem(LocalStorageKeys.SESSION_ID)) || null; this._eventId = localStorage.getItem(LocalStorageKeys.LAST_EVENT_ID) || 0; var now = new Date().getTime(); if (!this._sessionId || !this._lastEventTime || now - this._lastEventTime > this.options.sessionTimeout) { + this._newSession = true; this._sessionId = now; localStorage.setItem(LocalStorageKeys.SESSION_ID, this._sessionId); } @@ -226,6 +234,10 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config) { } }; +Amplitude.prototype.isNewSession = function() { + return this._newSession; +}; + Amplitude.prototype.nextEventId = function() { this._eventId++; return this._eventId; @@ -254,6 +266,31 @@ var _saveCookieData = function(scope) { }); }; +Amplitude._getUtmParam = function(name, query) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"); + var results = regex.exec(query); + return results === null ? undefined : decodeURIComponent(results[1].replace(/\+/g, " ")); +}; + +Amplitude._getUtmData = function(rawCookie, query) { + // Translate the utmz cookie format into url query string format. + var cookie = rawCookie ? '?' + rawCookie.split('.').slice(-1)[0].replace(/\|/g, '&') : ''; + + var fetchParam = function (queryName, query, cookieName, cookie) { + return Amplitude._getUtmParam(queryName, query) || + Amplitude._getUtmParam(cookieName, cookie); + }; + + return { + utm_source: fetchParam('utm_source', query, 'utmcsr', cookie), + utm_medium: fetchParam('utm_medium', query, 'utmcmd', cookie), + utm_campaign: fetchParam('utm_campaign', query, 'utmccn', cookie), + utm_term: fetchParam('utm_term', query, 'utmctr', cookie), + utm_content: fetchParam('utm_content', query, 'utmcct', cookie), + }; +}; + Amplitude.prototype.saveEvents = function() { try { localStorage.setItem(this.options.unsentKey, JSON.stringify(this._unsentEvents)); @@ -336,6 +373,11 @@ Amplitude.prototype.logEvent = function(eventType, eventProperties) { localStorage.setItem(LocalStorageKeys.LAST_EVENT_TIME, this._lastEventTime); localStorage.setItem(LocalStorageKeys.LAST_EVENT_ID, eventId); + // Add the utm properties, if any, onto the user properties. + var userProperties = {}; + object.merge(userProperties, this.options.userProperties || {}, this._utmProperties); + object.merge(userProperties, this._utmProperties); + eventProperties = eventProperties || {}; var event = { device_id: this.options.deviceId, @@ -351,7 +393,7 @@ Amplitude.prototype.logEvent = function(eventType, eventProperties) { device_model: ua.os.family, language: this.options.language, event_properties: eventProperties, - user_properties: this.options.userProperties || {}, + user_properties: userProperties, uuid: UUID(), library: { name: 'amplitude-js', diff --git a/amplitude.min.js b/amplitude.min.js index f2a29d7f..ef473f1d 100644 --- a/amplitude.min.js +++ b/amplitude.min.js @@ -1,2 +1,2 @@ -(function umd(require){if("object"==typeof exports){module.exports=require("1")}else if("function"==typeof define&&define.amd){define(function(){return require("1")})}else{this["amplitude"]=require("1")}})(function outer(modules,cache,entries){var global=function(){return this}();function require(name,jumped){if(cache[name])return cache[name].exports;if(modules[name])return call(name,require);throw new Error('cannot find module "'+name+'"')}function call(id,require){var m=cache[id]={exports:{}};var mod=modules[id];var name=mod[2];var fn=mod[0];fn.call(m.exports,function(req){var dep=modules[id][1][req];return require(dep?dep:req)},m,m.exports,outer,modules,cache,entries);if(name)cache[name]=cache[id];return cache[id].exports}for(var id in entries){if(entries[id]){global[entries[id]]=require(id)}else{require(id)}}require.duo=true;require.cache=cache;require.modules=modules;return require}({1:[function(require,module,exports){var Amplitude=require("./amplitude");var old=window.amplitude||{};var q=old._q||[];var instance=new Amplitude;for(var i=0;i0){this.sendEvents()}this._lastEventTime=parseInt(localStorage.getItem(LocalStorageKeys.LAST_EVENT_TIME))||null;this._sessionId=parseInt(localStorage.getItem(LocalStorageKeys.SESSION_ID))||null;this._eventId=localStorage.getItem(LocalStorageKeys.LAST_EVENT_ID)||0;var now=(new Date).getTime();if(!this._sessionId||!this._lastEventTime||now-this._lastEventTime>this.options.sessionTimeout){this._sessionId=now;localStorage.setItem(LocalStorageKeys.SESSION_ID,this._sessionId)}this._lastEventTime=now;localStorage.setItem(LocalStorageKeys.LAST_EVENT_TIME,this._lastEventTime)}catch(e){log(e)}};Amplitude.prototype.nextEventId=function(){this._eventId++;return this._eventId};var _loadCookieData=function(scope){var cookieData=Cookie.get(scope.options.cookieName);if(cookieData){if(cookieData.deviceId){scope.options.deviceId=cookieData.deviceId}if(cookieData.userId){scope.options.userId=cookieData.userId}if(cookieData.globalUserProperties){scope.options.userProperties=cookieData.globalUserProperties}}};var _saveCookieData=function(scope){Cookie.set(scope.options.cookieName,{deviceId:scope.options.deviceId,userId:scope.options.userId,globalUserProperties:scope.options.userProperties})};Amplitude.prototype.saveEvents=function(){try{localStorage.setItem(this.options.unsentKey,JSON.stringify(this._unsentEvents))}catch(e){}};Amplitude.prototype.setDomain=function(domain){try{Cookie.options({domain:domain});this.options.domain=Cookie.options().domain;_loadCookieData(this);_saveCookieData(this)}catch(e){log(e)}};Amplitude.prototype.setUserId=function(userId){try{this.options.userId=userId!==undefined&&userId!==null&&""+userId||null;_saveCookieData(this)}catch(e){log(e)}};Amplitude.prototype.setDeviceId=function(deviceId){try{if(deviceId){this.options.deviceId=""+deviceId;_saveCookieData(this)}}catch(e){log(e)}};Amplitude.prototype.setUserProperties=function(userProperties,opt_replace){try{if(opt_replace){this.options.userProperties=userProperties}else{this.options.userProperties=object.merge(this.options.userProperties||{},userProperties)}_saveCookieData(this)}catch(e){log(e)}};Amplitude.prototype.setVersionName=function(versionName){try{this.options.versionName=versionName}catch(e){log(e)}};Amplitude.prototype.logEvent=function(eventType,eventProperties){if(!eventType){return}try{var eventTime=(new Date).getTime();var eventId=this.nextEventId();var ua=this._ua;if(!this._sessionId||!this._lastEventTime||eventTime-this._lastEventTime>this.options.sessionTimeout){this._sessionId=eventTime;localStorage.setItem(LocalStorageKeys.SESSION_ID,this._sessionId)}this._lastEventTime=eventTime;localStorage.setItem(LocalStorageKeys.LAST_EVENT_TIME,this._lastEventTime);localStorage.setItem(LocalStorageKeys.LAST_EVENT_ID,eventId);eventProperties=eventProperties||{};var event={device_id:this.options.deviceId,user_id:this.options.userId||this.options.deviceId,timestamp:eventTime,event_id:eventId,session_id:this._sessionId||-1,event_type:eventType,version_name:this.options.versionName||null,platform:this.options.platform,os_name:ua.browser.family,os_version:ua.browser.version,device_model:ua.os.family,language:this.options.language,event_properties:eventProperties,user_properties:this.options.userProperties||{},uuid:UUID(),library:{name:"amplitude-js",version:this.__VERSION__}};this._unsentEvents.push(event);if(this.options.saveEvents){this.saveEvents()}this.sendEvents()}catch(e){log(e)}};Amplitude.prototype.sendEvents=function(){if(!this._sending){this._sending=true;var url=("https:"==window.location.protocol?"https":"http")+"://"+this.options.apiEndpoint+"/";var events=JSON.stringify(this._unsentEvents);var uploadTime=(new Date).getTime();var data={client:this.options.apiKey,e:events,v:API_VERSION,upload_time:uploadTime,checksum:md5(API_VERSION+this.options.apiKey+events+uploadTime)};var numEvents=this._unsentEvents.length;var scope=this;new Request(url,data).send(function(response){scope._sending=false;try{if(response=="success"){scope._unsentEvents.splice(0,numEvents);if(scope.options.saveEvents){scope.saveEvents()}if(scope._unsentEvents.length>0){scope.sendEvents()}}}catch(e){}})}};Amplitude.prototype.setGlobalUserProperties=Amplitude.prototype.setUserProperties;Amplitude.prototype.__VERSION__=version;module.exports=Amplitude},{"./base64":3,"./cookie":4,json:5,"./xhr":6,"./utf8":7,"./uuid":8,"./detect":9,"./language":10,"./localstorage":11,"./md5":12,object:13,"./version":14}],3:[function(require,module,exports){var UTF8=require("./utf8");var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(input){try{if(window.btoa&&window.atob){return window.btoa(unescape(encodeURIComponent(input)))}}catch(e){}return Base64._encode(input)},_encode:function(input){var output="";var chr1,chr2,chr3,enc1,enc2,enc3,enc4;var i=0;input=UTF8.encode(input);while(i>2;enc2=(chr1&3)<<4|chr2>>4;enc3=(chr2&15)<<2|chr3>>6;enc4=chr3&63;if(isNaN(chr2)){enc3=enc4=64}else if(isNaN(chr3)){enc4=64}output=output+Base64._keyStr.charAt(enc1)+Base64._keyStr.charAt(enc2)+Base64._keyStr.charAt(enc3)+Base64._keyStr.charAt(enc4)}return output},decode:function(input){try{if(window.btoa&&window.atob){return decodeURIComponent(escape(window.atob(input)))}}catch(e){}return Base64._decode(input)},_decode:function(input){var output="";var chr1,chr2,chr3;var enc1,enc2,enc3,enc4;var i=0;input=input.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(i>4;chr2=(enc2&15)<<4|enc3>>2;chr3=(enc3&3)<<6|enc4;output=output+String.fromCharCode(chr1);if(enc3!=64){output=output+String.fromCharCode(chr2)}if(enc4!=64){output=output+String.fromCharCode(chr3)}}output=UTF8.decode(output);return output}};module.exports=Base64},{"./utf8":7}],7:[function(require,module,exports){var UTF8={encode:function(s){s=s.replace(/\r\n/g,"\n");var utftext="";for(var n=0;n127&&c<2048){utftext+=String.fromCharCode(c>>6|192);utftext+=String.fromCharCode(c&63|128)}else{utftext+=String.fromCharCode(c>>12|224);utftext+=String.fromCharCode(c>>6&63|128);utftext+=String.fromCharCode(c&63|128)}}return utftext},decode:function(utftext){var s="";var i=0;var c=0,c1=0,c2=0;while(i191&&c<224){c1=utftext.charCodeAt(i+1);s+=String.fromCharCode((c&31)<<6|c1&63);i+=2}else{c1=utftext.charCodeAt(i+1);c2=utftext.charCodeAt(i+2);s+=String.fromCharCode((c&15)<<12|(c1&63)<<6|c2&63);i+=3}}return s}};module.exports=UTF8},{}],4:[function(require,module,exports){var Base64=require("./base64");var JSON=require("json");var topDomain=require("top-domain");var _options={expirationDays:undefined,domain:undefined};var reset=function(){_options={}};var options=function(opts){if(arguments.length===0){return _options}opts=opts||{};_options.expirationDays=opts.expirationDays;var domain=opts.domain!==undefined?opts.domain:"."+topDomain(window.location.href);var token=Math.random();_options.domain=domain;set("amplitude_test",token);var stored=get("amplitude_test");if(!stored||stored!=token){domain=null}remove("amplitude_test");_options.domain=domain};var _domainSpecific=function(name){var suffix="";if(_options.domain){suffix=_options.domain.charAt(0)=="."?_options.domain.substring(1):_options.domain}return name+suffix};var get=function(name){try{var nameEq=_domainSpecific(name)+"=";var ca=document.cookie.split(";");var value=null;for(var i=0;i>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,uuid)};module.exports=uuid},{}],9:[function(require,module,exports){var detect=function(root,undefined){if(!Array.prototype.map){Array.prototype.map=function(callback,thisArg){var T,A,k;if(this==null){throw new TypeError(" this is null or not defined")}var O=Object(this);var len=O.length>>>0;if(typeof callback!=="function"){throw new TypeError(callback+" is not a function")}if(thisArg){T=thisArg}A=new Array(len);k=0;while(k>>32-s,b)};var ff=function(a,b,c,d,x,s,t){return cmn(b&c|~b&d,a,b,x,s,t)};var gg=function(a,b,c,d,x,s,t){return cmn(b&d|c&~d,a,b,x,s,t)};var hh=function(a,b,c,d,x,s,t){return cmn(b^c^d,a,b,x,s,t)};var ii=function(a,b,c,d,x,s,t){return cmn(c^(b|~d),a,b,x,s,t)};var md51=function(s){s=UTF8.encode(s);var n=s.length,state=[1732584193,-271733879,-1732584194,271733878],i;for(i=64;i<=s.length;i+=64){md5cycle(state,md5blk(s.substring(i-64,i)))}s=s.substring(i-64);var tail=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(i=0;i>2]|=s.charCodeAt(i)<<(i%4<<3);tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(state,tail);for(i=0;i<16;i++)tail[i]=0}tail[14]=n*8;md5cycle(state,tail);return state};var md5blk=function(s){var md5blks=[],i;for(i=0;i<64;i+=4){md5blks[i>>2]=s.charCodeAt(i)+(s.charCodeAt(i+1)<<8)+(s.charCodeAt(i+2)<<16)+(s.charCodeAt(i+3)<<24)}return md5blks};var hex_chr="0123456789abcdef".split("");var rhex=function(n){var s="",j=0;for(;j<4;j++)s+=hex_chr[n>>j*8+4&15]+hex_chr[n>>j*8&15];return s};var hex=function(x){for(var i=0;i>16)+(y>>16)+(lsw>>16);return msw<<16|lsw&65535}}module.exports=md5},{"./utf8":7}],13:[function(require,module,exports){var has=Object.prototype.hasOwnProperty;exports.keys=Object.keys||function(obj){var keys=[];for(var key in obj){if(has.call(obj,key)){keys.push(key)}}return keys};exports.values=function(obj){var vals=[];for(var key in obj){if(has.call(obj,key)){vals.push(obj[key])}}return vals};exports.merge=function(a,b){for(var key in b){if(has.call(b,key)){a[key]=b[key]}}return a};exports.length=function(obj){return exports.keys(obj).length};exports.isEmpty=function(obj){return 0==exports.length(obj)}},{}],14:[function(require,module,exports){module.exports="2.0.3"},{}]},{},{1:""})); \ No newline at end of file +(function umd(require){if("object"==typeof exports){module.exports=require("1")}else if("function"==typeof define&&define.amd){define(function(){return require("1")})}else{this["amplitude"]=require("1")}})(function outer(modules,cache,entries){var global=function(){return this}();function require(name,jumped){if(cache[name])return cache[name].exports;if(modules[name])return call(name,require);throw new Error('cannot find module "'+name+'"')}function call(id,require){var m=cache[id]={exports:{}};var mod=modules[id];var name=mod[2];var fn=mod[0];fn.call(m.exports,function(req){var dep=modules[id][1][req];return require(dep?dep:req)},m,m.exports,outer,modules,cache,entries);if(name)cache[name]=cache[id];return cache[id].exports}for(var id in entries){if(entries[id]){global[entries[id]]=require(id)}else{require(id)}}require.duo=true;require.cache=cache;require.modules=modules;return require}({1:[function(require,module,exports){var Amplitude=require("./amplitude");var old=window.amplitude||{};var q=old._q||[];var instance=new Amplitude;for(var i=0;i0){this.sendEvents()}var utmParams=opt_config&&opt_config.utmParams||location.search;this._utmProperties=Amplitude._getUtmData(Cookie.get("__utmz"),utmParams);this._lastEventTime=parseInt(localStorage.getItem(LocalStorageKeys.LAST_EVENT_TIME))||null;this._sessionId=parseInt(localStorage.getItem(LocalStorageKeys.SESSION_ID))||null;this._eventId=localStorage.getItem(LocalStorageKeys.LAST_EVENT_ID)||0;var now=(new Date).getTime();if(!this._sessionId||!this._lastEventTime||now-this._lastEventTime>this.options.sessionTimeout){this._newSession=true;this._sessionId=now;localStorage.setItem(LocalStorageKeys.SESSION_ID,this._sessionId)}this._lastEventTime=now;localStorage.setItem(LocalStorageKeys.LAST_EVENT_TIME,this._lastEventTime)}catch(e){log(e)}};Amplitude.prototype.isNewSession=function(){return this._newSession};Amplitude.prototype.nextEventId=function(){this._eventId++;return this._eventId};var _loadCookieData=function(scope){var cookieData=Cookie.get(scope.options.cookieName);if(cookieData){if(cookieData.deviceId){scope.options.deviceId=cookieData.deviceId}if(cookieData.userId){scope.options.userId=cookieData.userId}if(cookieData.globalUserProperties){scope.options.userProperties=cookieData.globalUserProperties}}};var _saveCookieData=function(scope){Cookie.set(scope.options.cookieName,{deviceId:scope.options.deviceId,userId:scope.options.userId,globalUserProperties:scope.options.userProperties})};Amplitude._getUtmParam=function(name,query){name=name.replace(/[\[]/,"\\[").replace(/[\]]/,"\\]");var regex=new RegExp("[\\?&]"+name+"=([^&#]*)");var results=regex.exec(query);return results===null?undefined:decodeURIComponent(results[1].replace(/\+/g," "))};Amplitude._getUtmData=function(rawCookie,query){var cookie=rawCookie?"?"+rawCookie.split(".").slice(-1)[0].replace(/\|/g,"&"):"";var fetchParam=function(queryName,query,cookieName,cookie){return Amplitude._getUtmParam(queryName,query)||Amplitude._getUtmParam(cookieName,cookie)};return{utm_source:fetchParam("utm_source",query,"utmcsr",cookie),utm_medium:fetchParam("utm_medium",query,"utmcmd",cookie),utm_campaign:fetchParam("utm_campaign",query,"utmccn",cookie),utm_term:fetchParam("utm_term",query,"utmctr",cookie),utm_content:fetchParam("utm_content",query,"utmcct",cookie)}};Amplitude.prototype.saveEvents=function(){try{localStorage.setItem(this.options.unsentKey,JSON.stringify(this._unsentEvents))}catch(e){}};Amplitude.prototype.setDomain=function(domain){try{Cookie.options({domain:domain});this.options.domain=Cookie.options().domain;_loadCookieData(this);_saveCookieData(this)}catch(e){log(e)}};Amplitude.prototype.setUserId=function(userId){try{this.options.userId=userId!==undefined&&userId!==null&&""+userId||null;_saveCookieData(this)}catch(e){log(e)}};Amplitude.prototype.setDeviceId=function(deviceId){try{if(deviceId){this.options.deviceId=""+deviceId;_saveCookieData(this)}}catch(e){log(e)}};Amplitude.prototype.setUserProperties=function(userProperties,opt_replace){try{if(opt_replace){this.options.userProperties=userProperties}else{this.options.userProperties=object.merge(this.options.userProperties||{},userProperties)}_saveCookieData(this)}catch(e){log(e)}};Amplitude.prototype.setVersionName=function(versionName){try{this.options.versionName=versionName}catch(e){log(e)}};Amplitude.prototype.logEvent=function(eventType,eventProperties){if(!eventType){return}try{var eventTime=(new Date).getTime();var eventId=this.nextEventId();var ua=this._ua;if(!this._sessionId||!this._lastEventTime||eventTime-this._lastEventTime>this.options.sessionTimeout){this._sessionId=eventTime;localStorage.setItem(LocalStorageKeys.SESSION_ID,this._sessionId)}this._lastEventTime=eventTime;localStorage.setItem(LocalStorageKeys.LAST_EVENT_TIME,this._lastEventTime);localStorage.setItem(LocalStorageKeys.LAST_EVENT_ID,eventId);var userProperties={};object.merge(userProperties,this.options.userProperties||{},this._utmProperties);object.merge(userProperties,this._utmProperties);eventProperties=eventProperties||{};var event={device_id:this.options.deviceId,user_id:this.options.userId||this.options.deviceId,timestamp:eventTime,event_id:eventId,session_id:this._sessionId||-1,event_type:eventType,version_name:this.options.versionName||null,platform:this.options.platform,os_name:ua.browser.family,os_version:ua.browser.version,device_model:ua.os.family,language:this.options.language,event_properties:eventProperties,user_properties:userProperties,uuid:UUID(),library:{name:"amplitude-js",version:this.__VERSION__}};this._unsentEvents.push(event);if(this.options.saveEvents){this.saveEvents()}this.sendEvents()}catch(e){log(e)}};Amplitude.prototype.sendEvents=function(){if(!this._sending){this._sending=true;var url=("https:"==window.location.protocol?"https":"http")+"://"+this.options.apiEndpoint+"/";var events=JSON.stringify(this._unsentEvents);var uploadTime=(new Date).getTime();var data={client:this.options.apiKey,e:events,v:API_VERSION,upload_time:uploadTime,checksum:md5(API_VERSION+this.options.apiKey+events+uploadTime)};var numEvents=this._unsentEvents.length;var scope=this;new Request(url,data).send(function(response){scope._sending=false;try{if(response=="success"){scope._unsentEvents.splice(0,numEvents);if(scope.options.saveEvents){scope.saveEvents()}if(scope._unsentEvents.length>0){scope.sendEvents()}}}catch(e){}})}};Amplitude.prototype.setGlobalUserProperties=Amplitude.prototype.setUserProperties;Amplitude.prototype.__VERSION__=version;module.exports=Amplitude},{"./base64":3,"./cookie":4,json:5,"./xhr":6,"./utf8":7,"./uuid":8,"./detect":9,"./language":10,"./localstorage":11,"./md5":12,object:13,"./version":14}],3:[function(require,module,exports){var UTF8=require("./utf8");var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(input){try{if(window.btoa&&window.atob){return window.btoa(unescape(encodeURIComponent(input)))}}catch(e){}return Base64._encode(input)},_encode:function(input){var output="";var chr1,chr2,chr3,enc1,enc2,enc3,enc4;var i=0;input=UTF8.encode(input);while(i>2;enc2=(chr1&3)<<4|chr2>>4;enc3=(chr2&15)<<2|chr3>>6;enc4=chr3&63;if(isNaN(chr2)){enc3=enc4=64}else if(isNaN(chr3)){enc4=64}output=output+Base64._keyStr.charAt(enc1)+Base64._keyStr.charAt(enc2)+Base64._keyStr.charAt(enc3)+Base64._keyStr.charAt(enc4)}return output},decode:function(input){try{if(window.btoa&&window.atob){return decodeURIComponent(escape(window.atob(input)))}}catch(e){}return Base64._decode(input)},_decode:function(input){var output="";var chr1,chr2,chr3;var enc1,enc2,enc3,enc4;var i=0;input=input.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(i>4;chr2=(enc2&15)<<4|enc3>>2;chr3=(enc3&3)<<6|enc4;output=output+String.fromCharCode(chr1);if(enc3!=64){output=output+String.fromCharCode(chr2)}if(enc4!=64){output=output+String.fromCharCode(chr3)}}output=UTF8.decode(output);return output}};module.exports=Base64},{"./utf8":7}],7:[function(require,module,exports){var UTF8={encode:function(s){s=s.replace(/\r\n/g,"\n");var utftext="";for(var n=0;n127&&c<2048){utftext+=String.fromCharCode(c>>6|192);utftext+=String.fromCharCode(c&63|128)}else{utftext+=String.fromCharCode(c>>12|224);utftext+=String.fromCharCode(c>>6&63|128);utftext+=String.fromCharCode(c&63|128)}}return utftext},decode:function(utftext){var s="";var i=0;var c=0,c1=0,c2=0;while(i191&&c<224){c1=utftext.charCodeAt(i+1);s+=String.fromCharCode((c&31)<<6|c1&63);i+=2}else{c1=utftext.charCodeAt(i+1);c2=utftext.charCodeAt(i+2);s+=String.fromCharCode((c&15)<<12|(c1&63)<<6|c2&63);i+=3}}return s}};module.exports=UTF8},{}],4:[function(require,module,exports){var Base64=require("./base64");var JSON=require("json");var topDomain=require("top-domain");var _options={expirationDays:undefined,domain:undefined};var reset=function(){_options={}};var options=function(opts){if(arguments.length===0){return _options}opts=opts||{};_options.expirationDays=opts.expirationDays;var domain=opts.domain!==undefined?opts.domain:"."+topDomain(window.location.href);var token=Math.random();_options.domain=domain;set("amplitude_test",token);var stored=get("amplitude_test");if(!stored||stored!=token){domain=null}remove("amplitude_test");_options.domain=domain};var _domainSpecific=function(name){var suffix="";if(_options.domain){suffix=_options.domain.charAt(0)=="."?_options.domain.substring(1):_options.domain}return name+suffix};var get=function(name){try{var nameEq=_domainSpecific(name)+"=";var ca=document.cookie.split(";");var value=null;for(var i=0;i>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,uuid)};module.exports=uuid},{}],9:[function(require,module,exports){var detect=function(root,undefined){if(!Array.prototype.map){Array.prototype.map=function(callback,thisArg){var T,A,k;if(this==null){throw new TypeError(" this is null or not defined")}var O=Object(this);var len=O.length>>>0;if(typeof callback!=="function"){throw new TypeError(callback+" is not a function")}if(thisArg){T=thisArg}A=new Array(len);k=0;while(k>>32-s,b)};var ff=function(a,b,c,d,x,s,t){return cmn(b&c|~b&d,a,b,x,s,t)};var gg=function(a,b,c,d,x,s,t){return cmn(b&d|c&~d,a,b,x,s,t)};var hh=function(a,b,c,d,x,s,t){return cmn(b^c^d,a,b,x,s,t)};var ii=function(a,b,c,d,x,s,t){return cmn(c^(b|~d),a,b,x,s,t)};var md51=function(s){s=UTF8.encode(s);var n=s.length,state=[1732584193,-271733879,-1732584194,271733878],i;for(i=64;i<=s.length;i+=64){md5cycle(state,md5blk(s.substring(i-64,i)))}s=s.substring(i-64);var tail=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(i=0;i>2]|=s.charCodeAt(i)<<(i%4<<3);tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(state,tail);for(i=0;i<16;i++)tail[i]=0}tail[14]=n*8;md5cycle(state,tail);return state};var md5blk=function(s){var md5blks=[],i;for(i=0;i<64;i+=4){md5blks[i>>2]=s.charCodeAt(i)+(s.charCodeAt(i+1)<<8)+(s.charCodeAt(i+2)<<16)+(s.charCodeAt(i+3)<<24)}return md5blks};var hex_chr="0123456789abcdef".split("");var rhex=function(n){var s="",j=0;for(;j<4;j++)s+=hex_chr[n>>j*8+4&15]+hex_chr[n>>j*8&15];return s};var hex=function(x){for(var i=0;i>16)+(y>>16)+(lsw>>16);return msw<<16|lsw&65535}}module.exports=md5},{"./utf8":7}],13:[function(require,module,exports){var has=Object.prototype.hasOwnProperty;exports.keys=Object.keys||function(obj){var keys=[];for(var key in obj){if(has.call(obj,key)){keys.push(key)}}return keys};exports.values=function(obj){var vals=[];for(var key in obj){if(has.call(obj,key)){vals.push(obj[key])}}return vals};exports.merge=function(a,b){for(var key in b){if(has.call(b,key)){a[key]=b[key]}}return a};exports.length=function(obj){return exports.keys(obj).length};exports.isEmpty=function(obj){return 0==exports.length(obj)}},{}],14:[function(require,module,exports){module.exports="2.0.3"},{}]},{},{1:""})); \ No newline at end of file diff --git a/src/amplitude.js b/src/amplitude.js index 11de82ec..66c289f0 100644 --- a/src/amplitude.js +++ b/src/amplitude.js @@ -55,6 +55,8 @@ Amplitude.prototype._newSession = false; * opt_userId An identifier for this user * opt_config Configuration options * - saveEvents (boolean) Whether to save events to local storage. Defaults to true. + * - utmParams (string) Optional utm data in query string format. + * Pulled from location.search otherwise. */ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config) { try { @@ -102,6 +104,10 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config) { this.sendEvents(); } + // Parse the utm properties out of cookies and query for adding to user properties. + var utmParams = opt_config && opt_config.utmParams || location.search; + this._utmProperties = Amplitude._getUtmData(Cookie.get('__utmz'), utmParams); + this._lastEventTime = parseInt(localStorage.getItem(LocalStorageKeys.LAST_EVENT_TIME)) || null; this._sessionId = parseInt(localStorage.getItem(LocalStorageKeys.SESSION_ID)) || null; this._eventId = localStorage.getItem(LocalStorageKeys.LAST_EVENT_ID) || 0; @@ -150,6 +156,31 @@ var _saveCookieData = function(scope) { }); }; +Amplitude._getUtmParam = function(name, query) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"); + var results = regex.exec(query); + return results === null ? undefined : decodeURIComponent(results[1].replace(/\+/g, " ")); +}; + +Amplitude._getUtmData = function(rawCookie, query) { + // Translate the utmz cookie format into url query string format. + var cookie = rawCookie ? '?' + rawCookie.split('.').slice(-1)[0].replace(/\|/g, '&') : ''; + + var fetchParam = function (queryName, query, cookieName, cookie) { + return Amplitude._getUtmParam(queryName, query) || + Amplitude._getUtmParam(cookieName, cookie); + }; + + return { + utm_source: fetchParam('utm_source', query, 'utmcsr', cookie), + utm_medium: fetchParam('utm_medium', query, 'utmcmd', cookie), + utm_campaign: fetchParam('utm_campaign', query, 'utmccn', cookie), + utm_term: fetchParam('utm_term', query, 'utmctr', cookie), + utm_content: fetchParam('utm_content', query, 'utmcct', cookie), + }; +}; + Amplitude.prototype.saveEvents = function() { try { localStorage.setItem(this.options.unsentKey, JSON.stringify(this._unsentEvents)); @@ -232,6 +263,11 @@ Amplitude.prototype.logEvent = function(eventType, eventProperties) { localStorage.setItem(LocalStorageKeys.LAST_EVENT_TIME, this._lastEventTime); localStorage.setItem(LocalStorageKeys.LAST_EVENT_ID, eventId); + // Add the utm properties, if any, onto the user properties. + var userProperties = {}; + object.merge(userProperties, this.options.userProperties || {}, this._utmProperties); + object.merge(userProperties, this._utmProperties); + eventProperties = eventProperties || {}; var event = { device_id: this.options.deviceId, @@ -247,7 +283,7 @@ Amplitude.prototype.logEvent = function(eventType, eventProperties) { device_model: ua.os.family, language: this.options.language, event_properties: eventProperties, - user_properties: this.options.userProperties || {}, + user_properties: userProperties, uuid: UUID(), library: { name: 'amplitude-js', diff --git a/test/amplitude.js b/test/amplitude.js index d89757ec..9c0d5d1a 100644 --- a/test/amplitude.js +++ b/test/amplitude.js @@ -187,6 +187,81 @@ describe('Amplitude', function() { }); }); + describe('gatherUtm', function() { + beforeEach(function() { + amplitude.init(apiKey); + }); + + afterEach(function() { + reset(); + }); + + it('should add utm params to the user properties', function() { + cookie.set('__utmz', '133232535.1424926227.1.1.utmcct=top&utmccn=new'); + + reset(); + amplitude.init(apiKey, undefined, { + utmParams: '?utm_source=amplitude&utm_medium=email&utm_term=terms' + }); + + amplitude.setUserProperties({user_prop: true}); + amplitude.logEvent('UTM Test Event', {}); + + assert.lengthOf(server.requests, 1); + var events = JSON.parse(querystring.parse(server.requests[0].requestBody).e); + assert.deepEqual(events[0].user_properties, { + user_prop: true, + utm_campaign: 'new', + utm_content: 'top', + utm_medium: 'email', + utm_source: 'amplitude', + utm_term: 'terms' + }); + + }); + + it('should get utm params from the query string', function() { + var query = '?utm_source=amplitude&utm_medium=email&utm_term=terms' + + '&utm_content=top&utm_campaign=new'; + var utms = Amplitude._getUtmData('', query); + assert.deepEqual(utms, { + utm_campaign: 'new', + utm_content: 'top', + utm_medium: 'email', + utm_source: 'amplitude', + utm_term: 'terms' + }); + }); + + it('should get utm params from the cookie string', function() { + var cookie = '133232535.1424926227.1.1.utmcsr=google|utmccn=(organic)' + + '|utmcmd=organic|utmctr=(none)|utmcct=link'; + var utms = Amplitude._getUtmData(cookie, ''); + assert.deepEqual(utms, { + utm_campaign: '(organic)', + utm_content: 'link', + utm_medium: 'organic', + utm_source: 'google', + utm_term: '(none)' + }); + }); + + it('should prefer utm params from the query string', function() { + var query = '?utm_source=amplitude&utm_medium=email&utm_term=terms' + + '&utm_content=top&utm_campaign=new'; + var cookie = '133232535.1424926227.1.1.utmcsr=google|utmccn=(organic)' + + '|utmcmd=organic|utmctr=(none)|utmcct=link'; + var utms = Amplitude._getUtmData(cookie, query); + assert.deepEqual(utms, { + utm_campaign: 'new', + utm_content: 'top', + utm_medium: 'email', + utm_source: 'amplitude', + utm_term: 'terms' + }); + }); + }); + describe('sessionId', function() { var clock; From 4b6d44229fc2f7dae62a9c8586705cdfa97f1ef8 Mon Sep 17 00:00:00 2001 From: Allan Carroll Date: Thu, 26 Feb 2015 18:17:48 -0800 Subject: [PATCH 2/2] Remove extraneous parameter to object.merge --- src/amplitude.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/amplitude.js b/src/amplitude.js index 66c289f0..4e8ce0d7 100644 --- a/src/amplitude.js +++ b/src/amplitude.js @@ -265,7 +265,7 @@ Amplitude.prototype.logEvent = function(eventType, eventProperties) { // Add the utm properties, if any, onto the user properties. var userProperties = {}; - object.merge(userProperties, this.options.userProperties || {}, this._utmProperties); + object.merge(userProperties, this.options.userProperties || {}); object.merge(userProperties, this._utmProperties); eventProperties = eventProperties || {};