diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c29739b..43dfa7f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * Add getSessionId helper method to fetch the current sessionId. * Add support for append user property operation. +* Add tracking of each user's initial_referrer property (which is captured as a set once operation). Referrer property captured once per user session. ## 2.7.0 (December 1, 2015) diff --git a/README.md b/README.md index edac2f80..192ae1e9 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,11 @@ If your app has its own login system that you want to track users with, you can amplitude.setUserId('USER_ID_HERE'); ``` -A user's data will be merged on the backend so that any events up to that point from the same browser will be tracked under the same user. +A user's data will be merged on the backend so that any events up to that point from the same browser will be tracked under the same user. Note: if a user logs out, or you want to log the events under an anonymous user, you may set the userId to `null` like so: + +```javascript +amplitude.setUserId(null); // not string 'null' +``` You can also add the user ID as an argument to the `init` call: @@ -62,7 +66,7 @@ eventProperties.key = 'value'; amplitude.logEvent('EVENT_IDENTIFIER_HERE', eventProperties); ``` -# User Property Operations # +# User Properties and User Property Operations # The SDK supports the operations `set`, `setOnce`, `unset`, and `add` on individual user properties. The operations are declared via a provided `Identify` interface. Multiple operations can be chained together in a single `Identify` object. The `Identify` object is then passed to the Amplitude client to send to the server. The results of the operations will be visible immediately in the dashboard, and take effect for events logged after. @@ -114,6 +118,18 @@ var identify = new amplitude.Identify() amplitude.identify(identify); ``` +### Arrays in User Properties ### + +The SDK supports arrays in user properties. Any of the user property operations above (with the exception of `add`) can accept a Javascript array. You can directly `set` arrays, or use `append` to generate an array. + +```javascript +var identify = new amplitude.Identify() + .set('colors', ['rose', 'gold']) + .append('ab-tests', 'campaign_a') + .append('existing_list', [4, 5]); +amplitude.identify(identify); +``` + ### Setting Multiple Properties with `setUserProperties` ### You may use `setUserProperties` shorthand to set multiple user properties at once. This method is simply a wrapper around `Identify.set` and `identify`. @@ -176,7 +192,7 @@ amplitude.init('YOUR_API_KEY_HERE', null, { | savedMaxCount | Maximum number of events to save in localStorage. If more events are logged while offline, old events are removed. | 1000 | | uploadBatchSize | Maximum number of events to send to the server per request. | 100 | | includeUtm | If `true`, finds utm parameters in the query string or the __utmz cookie, parses, and includes them as user propeties on all events uploaded. | `false` | -| includeReferrer | If `true`, includes `referrer` and `referring_domain` as user propeties on all events uploaded. | `false` | +| includeReferrer | If `true`, captures the `referrer` and `referring_domain` for each session, as well as the user's `initial_referrer` and `initial_referring_domain` via a set once operation. | `false` | | batchEvents | If `true`, events are batched together and uploaded only when the number of unsent events is greater than or equal to `eventUploadThreshold` or after `eventUploadPeriodMillis` milliseconds have passed since the first unsent event was logged. | `false` | | eventUploadThreshold | Minimum number of events to batch together per request if `batchEvents` is `true`. | 30 | | eventUploadPeriodMillis | Amount of time in milliseconds that the SDK waits before uploading events if `batchEvents` is `true`. | 30\*1000 (30 sec) | diff --git a/amplitude.js b/amplitude.js index fad18568..f8700b42 100644 --- a/amplitude.js +++ b/amplitude.js @@ -149,7 +149,9 @@ var LocalStorageKeys = { LAST_SEQUENCE_NUMBER: 'amplitude_lastSequenceNumber', LAST_EVENT_TIME: 'amplitude_lastEventTime', SESSION_ID: 'amplitude_sessionId', + REFERRER: 'amplitude_referrer', + // Used in cookie as well DEVICE_ID: 'amplitude_deviceId', USER_ID: 'amplitude_userId', OPT_OUT: 'amplitude_optOut' @@ -256,6 +258,10 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config, callback) { if (this.options.includeUtm) { this._initUtmData(); } + + if (this.options.includeReferrer) { + this._saveReferrer(this._getReferrer()); + } } catch (e) { log(e); } @@ -467,12 +473,39 @@ Amplitude.prototype._getReferrer = function() { return document.referrer; }; -Amplitude.prototype._getReferringDomain = function() { - var parts = this._getReferrer().split("/"); +Amplitude.prototype._getReferringDomain = function(referrer) { + if (referrer === null || referrer === undefined || referrer === '') { + return null; + } + var parts = referrer.split('/'); if (parts.length >= 3) { return parts[2]; } - return ""; + return null; +}; + +// since user properties are propagated on the server, only send once per session, don't need to send with every event +Amplitude.prototype._saveReferrer = function(referrer) { + if (referrer === null || referrer === undefined || referrer === '') { + return; + } + + // always setOnce initial referrer + var referring_domain = this._getReferringDomain(referrer); + var identify = new Identify().setOnce('initial_referrer', referrer); + identify.setOnce('initial_referring_domain', referring_domain); + + // only save referrer if not already in session storage or if storage disabled + var hasSessionStorage = window.sessionStorage ? true : false; + if ((hasSessionStorage && !window.sessionStorage.getItem(LocalStorageKeys.REFERRER)) || !hasSessionStorage) { + identify.set('referrer', referrer).set('referring_domain', referring_domain); + + if (hasSessionStorage) { + window.sessionStorage.setItem(LocalStorageKeys.REFERRER, referrer); + } + } + + this.identify(identify); }; Amplitude.prototype.saveEvents = function() { @@ -628,14 +661,6 @@ Amplitude.prototype._logEvent = function(eventType, eventProperties, apiProperti // Only add utm properties to user properties for events if (eventType !== IDENTIFY_EVENT) { object.merge(userProperties, this._utmProperties); - - // Add referral info onto the user properties - if (this.options.includeReferrer) { - object.merge(userProperties, { - 'referrer': this._getReferrer(), - 'referring_domain': this._getReferringDomain() - }); - } } apiProperties = apiProperties || {}; @@ -2545,15 +2570,25 @@ module.exports = function(val){ if (val !== val) return 'nan'; if (val && val.nodeType === 1) return 'element'; - if (typeof Buffer != 'undefined' && Buffer.isBuffer(val)) return 'buffer'; + if (isBuffer(val)) return 'buffer'; val = val.valueOf ? val.valueOf() - : Object.prototype.valueOf.apply(val) + : Object.prototype.valueOf.apply(val); return typeof val; }; +// code borrowed from https://github.com/feross/is-buffer/blob/master/index.js +function isBuffer(obj) { + return !!(obj != null && + (obj._isBuffer || // For Safari 5-7 (missing Object.prototype.constructor) + (obj.constructor && + typeof obj.constructor.isBuffer === 'function' && + obj.constructor.isBuffer(obj)) + )) +} + }, {}], 10: [function(require, module, exports) { /* jshint eqeqeq: false, forin: false */ diff --git a/amplitude.min.js b/amplitude.min.js index b1d6b2f9..d1c1b27d 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 instance=new Amplitude;instance._q=old._q||[];module.exports=instance},{"./amplitude":2}],2:[function(require,module,exports){var cookieStorage=require("./cookiestorage");var JSON=require("json");var language=require("./language");var localStorage=require("./localstorage");var md5=require("JavaScript-MD5");var object=require("object");var Request=require("./xhr");var UAParser=require("ua-parser-js");var UUID=require("./uuid");var version=require("./version");var Identify=require("./identify");var type=require("./type");var log=function(s){console.log("[Amplitude] "+s)};var IDENTIFY_EVENT="$identify";var API_VERSION=2;var MAX_STRING_LENGTH=1024;var DEFAULT_OPTIONS={apiEndpoint:"api.amplitude.com",cookieExpiration:365*10,cookieName:"amplitude_id",domain:undefined,includeUtm:false,language:language.language,optOut:false,platform:"Web",savedMaxCount:1e3,saveEvents:true,sessionTimeout:30*60*1e3,unsentKey:"amplitude_unsent",unsentIdentifyKey:"amplitude_unsent_identify",uploadBatchSize:100,batchEvents:false,eventUploadThreshold:30,eventUploadPeriodMillis:30*1e3};var LocalStorageKeys={LAST_EVENT_ID:"amplitude_lastEventId",LAST_IDENTIFY_ID:"amplitude_lastIdentifyId",LAST_SEQUENCE_NUMBER:"amplitude_lastSequenceNumber",LAST_EVENT_TIME:"amplitude_lastEventTime",SESSION_ID:"amplitude_sessionId",DEVICE_ID:"amplitude_deviceId",USER_ID:"amplitude_userId",OPT_OUT:"amplitude_optOut"};var Amplitude=function(){this._unsentEvents=[];this._unsentIdentifys=[];this._ua=new UAParser(navigator.userAgent).getResult();this.options=object.merge({},DEFAULT_OPTIONS);this.cookieStorage=(new cookieStorage).getStorage();this._q=[]};Amplitude.prototype._eventId=0;Amplitude.prototype._identifyId=0;Amplitude.prototype._sequenceNumber=0;Amplitude.prototype._sending=false;Amplitude.prototype._lastEventTime=null;Amplitude.prototype._sessionId=null;Amplitude.prototype._newSession=false;Amplitude.prototype._updateScheduled=false;Amplitude.prototype.Identify=Identify;Amplitude.prototype.init=function(apiKey,opt_userId,opt_config,callback){try{this.options.apiKey=apiKey;if(opt_config){if(opt_config.saveEvents!==undefined){this.options.saveEvents=!!opt_config.saveEvents}if(opt_config.domain!==undefined){this.options.domain=opt_config.domain}if(opt_config.includeUtm!==undefined){this.options.includeUtm=!!opt_config.includeUtm}if(opt_config.includeReferrer!==undefined){this.options.includeReferrer=!!opt_config.includeReferrer}if(opt_config.batchEvents!==undefined){this.options.batchEvents=!!opt_config.batchEvents}this.options.platform=opt_config.platform||this.options.platform;this.options.language=opt_config.language||this.options.language;this.options.sessionTimeout=opt_config.sessionTimeout||this.options.sessionTimeout;this.options.uploadBatchSize=opt_config.uploadBatchSize||this.options.uploadBatchSize;this.options.eventUploadThreshold=opt_config.eventUploadThreshold||this.options.eventUploadThreshold;this.options.savedMaxCount=opt_config.savedMaxCount||this.options.savedMaxCount;this.options.eventUploadPeriodMillis=opt_config.eventUploadPeriodMillis||this.options.eventUploadPeriodMillis}this.cookieStorage.options({expirationDays:this.options.cookieExpiration,domain:this.options.domain});this.options.domain=this.cookieStorage.options().domain;_migrateLocalStorageDataToCookie(this);_loadCookieData(this);this.options.deviceId=opt_config&&opt_config.deviceId!==undefined&&opt_config.deviceId!==null&&opt_config.deviceId||this.options.deviceId||UUID();this.options.userId=opt_userId!==undefined&&opt_userId!==null&&opt_userId||this.options.userId||null;this._lastEventTime=this._lastEventTime||parseInt(localStorage.getItem(LocalStorageKeys.LAST_EVENT_TIME))||null;this._sessionId=this._sessionId||parseInt(localStorage.getItem(LocalStorageKeys.SESSION_ID))||null;this._eventId=this._eventId||parseInt(localStorage.getItem(LocalStorageKeys.LAST_EVENT_ID))||0;this._identifyId=this._identifyId||parseInt(localStorage.getItem(LocalStorageKeys.LAST_IDENTIFY_ID))||0;this._sequenceNumber=this._sequenceNumber||parseInt(localStorage.getItem(LocalStorageKeys.LAST_SEQUENCE_NUMBER))||0;var now=(new Date).getTime();if(!this._sessionId||!this._lastEventTime||now-this._lastEventTime>this.options.sessionTimeout){this._newSession=true;this._sessionId=now}this._lastEventTime=now;_saveCookieData(this);_clearSessionAndEventTrackingFromLocalStorage();if(this.options.saveEvents){this._loadSavedUnsentEvents(this.options.unsentKey,"_unsentEvents");this._loadSavedUnsentEvents(this.options.unsentIdentifyKey,"_unsentIdentifys")}this._sendEventsIfReady();if(this.options.includeUtm){this._initUtmData()}}catch(e){log(e)}if(callback&&type(callback)==="function"){callback()}};Amplitude.prototype.runQueuedFunctions=function(){for(var i=0;i=this.options.eventUploadThreshold){this.sendEvents(callback);return true}if(!this._updateScheduled){this._updateScheduled=true;setTimeout(function(){this._updateScheduled=false;this.sendEvents()}.bind(this),this.options.eventUploadPeriodMillis)}return false};var _migrateLocalStorageDataToCookie=function(scope){var cookieData=scope.cookieStorage.get(scope.options.cookieName);if(cookieData&&cookieData.deviceId){return}var cookieDeviceId=cookieData&&cookieData.deviceId||null;var cookieUserId=cookieData&&cookieData.userId||null;var cookieOptOut=cookieData&&cookieData.optOut!==null&&cookieData.optOut!==undefined?cookieData.optOut:null;var keySuffix="_"+scope.options.apiKey.slice(0,6);var localStorageDeviceId=localStorage.getItem(LocalStorageKeys.DEVICE_ID+keySuffix);if(localStorageDeviceId){localStorage.removeItem(LocalStorageKeys.DEVICE_ID+keySuffix)}var localStorageUserId=localStorage.getItem(LocalStorageKeys.USER_ID+keySuffix);if(localStorageUserId){localStorage.removeItem(LocalStorageKeys.USER_ID+keySuffix)}var localStorageOptOut=localStorage.getItem(LocalStorageKeys.OPT_OUT+keySuffix);if(localStorageOptOut!==null&&localStorageOptOut!==undefined){localStorage.removeItem(LocalStorageKeys.OPT_OUT+keySuffix);localStorageOptOut=String(localStorageOptOut)==="true"}scope.cookieStorage.set(scope.options.cookieName,{deviceId:cookieDeviceId||localStorageDeviceId,userId:cookieUserId||localStorageUserId,optOut:cookieOptOut!==undefined&&cookieOptOut!==null?cookieOptOut:localStorageOptOut})};var _loadCookieData=function(scope){var cookieData=scope.cookieStorage.get(scope.options.cookieName);if(cookieData){if(cookieData.deviceId){scope.options.deviceId=cookieData.deviceId}if(cookieData.userId){scope.options.userId=cookieData.userId}if(cookieData.optOut!==null&&cookieData.optOut!==undefined){scope.options.optOut=cookieData.optOut}if(cookieData.sessionId){scope._sessionId=parseInt(cookieData.sessionId)}if(cookieData.lastEventTime){scope._lastEventTime=parseInt(cookieData.lastEventTime)}if(cookieData.eventId){scope._eventId=parseInt(cookieData.eventId)}if(cookieData.identifyId){scope._identifyId=parseInt(cookieData.identifyId)}if(cookieData.sequenceNumber){scope._sequenceNumber=parseInt(cookieData.sequenceNumber)}}};var _saveCookieData=function(scope){scope.cookieStorage.set(scope.options.cookieName,{deviceId:scope.options.deviceId,userId:scope.options.userId,optOut:scope.options.optOut,sessionId:scope._sessionId,lastEventTime:scope._lastEventTime,eventId:scope._eventId,identifyId:scope._identifyId,sequenceNumber:scope._sequenceNumber})};var _clearSessionAndEventTrackingFromLocalStorage=function(){localStorage.removeItem(LocalStorageKeys.SESSION_ID);localStorage.removeItem(LocalStorageKeys.LAST_EVENT_TIME);localStorage.removeItem(LocalStorageKeys.LAST_EVENT_ID);localStorage.removeItem(LocalStorageKeys.LAST_IDENTIFY_ID);localStorage.removeItem(LocalStorageKeys.LAST_SEQUENCE_NUMBER)};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._initUtmData=function(queryParams,cookieParams){queryParams=queryParams||location.search;cookieParams=cookieParams||this.cookieStorage.get("__utmz");this._utmProperties=Amplitude._getUtmData(cookieParams,queryParams)};Amplitude.prototype._getReferrer=function(){return document.referrer};Amplitude.prototype._getReferringDomain=function(){var parts=this._getReferrer().split("/");if(parts.length>=3){return parts[2]}return""};Amplitude.prototype.saveEvents=function(){try{localStorage.setItem(this.options.unsentKey,JSON.stringify(this._unsentEvents));localStorage.setItem(this.options.unsentIdentifyKey,JSON.stringify(this._unsentIdentifys))}catch(e){}};Amplitude.prototype.setDomain=function(domain){try{this.cookieStorage.options({domain:domain});this.options.domain=this.cookieStorage.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.setOptOut=function(enable){try{this.options.optOut=enable;_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){var identify=new Identify;for(var property in userProperties){if(userProperties.hasOwnProperty(property)){identify.set(property,userProperties[property])}}this.identify(identify)};Amplitude.prototype.identify=function(identify){if(type(identify)==="object"&&"_q"in identify){var instance=new Identify;for(var i=0;i0){this._logEvent(IDENTIFY_EVENT,null,null,identify.userPropertiesOperations)}};Amplitude.prototype.setVersionName=function(versionName){try{this.options.versionName=versionName}catch(e){log(e)}};Amplitude.prototype._truncate=function(value){if(type(value)==="array"){for(var i=0;iMAX_STRING_LENGTH?value.substring(0,MAX_STRING_LENGTH):value}return value};Amplitude.prototype._logEvent=function(eventType,eventProperties,apiProperties,userProperties,callback){if(type(callback)!=="function"){callback=null}if(!eventType||this.options.optOut){if(callback){callback(0,"No request sent")}return}try{var eventId;if(eventType===IDENTIFY_EVENT){eventId=this.nextIdentifyId()}else{eventId=this.nextEventId()}var sequenceNumber=this.nextSequenceNumber();var eventTime=(new Date).getTime();var ua=this._ua;if(!this._sessionId||!this._lastEventTime||eventTime-this._lastEventTime>this.options.sessionTimeout){this._sessionId=eventTime}this._lastEventTime=eventTime;_saveCookieData(this);userProperties=userProperties||{};if(eventType!==IDENTIFY_EVENT){object.merge(userProperties,this._utmProperties);if(this.options.includeReferrer){object.merge(userProperties,{referrer:this._getReferrer(),referring_domain:this._getReferringDomain()})}}apiProperties=apiProperties||{};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.name||null,os_version:ua.browser.major||null,device_model:ua.os.name||null,language:this.options.language,api_properties:apiProperties,event_properties:this._truncate(eventProperties),user_properties:this._truncate(userProperties),uuid:UUID(),library:{name:"amplitude-js",version:this.__VERSION__},sequence_number:sequenceNumber};if(eventType===IDENTIFY_EVENT){this._unsentIdentifys.push(event);this._limitEventsQueued(this._unsentIdentifys)}else{this._unsentEvents.push(event);this._limitEventsQueued(this._unsentEvents)}if(this.options.saveEvents){this.saveEvents()}if(!this._sendEventsIfReady(callback)&&callback){callback(0,"No request sent")}return eventId}catch(e){log(e)}};Amplitude.prototype._limitEventsQueued=function(queue){if(queue.length>this.options.savedMaxCount){queue.splice(0,queue.length-this.options.savedMaxCount)}};Amplitude.prototype.logEvent=function(eventType,eventProperties,callback){return this._logEvent(eventType,eventProperties,null,null,callback)};var _isNumber=function(n){return!isNaN(parseFloat(n))&&isFinite(n)};Amplitude.prototype.logRevenue=function(price,quantity,product){if(!_isNumber(price)||quantity!==undefined&&!_isNumber(quantity)){return}return this._logEvent("revenue_amount",{},{productId:product,special:"revenue_amount",quantity:quantity||1,price:price})};Amplitude.prototype.removeEvents=function(maxEventId,maxIdentifyId){if(maxEventId>=0){var filteredEvents=[];for(var i=0;imaxEventId){filteredEvents.push(this._unsentEvents[i])}}this._unsentEvents=filteredEvents}if(maxIdentifyId>=0){var filteredIdentifys=[];for(var j=0;jmaxIdentifyId){filteredIdentifys.push(this._unsentIdentifys[j])}}this._unsentIdentifys=filteredIdentifys}};Amplitude.prototype.sendEvents=function(callback){if(!this._sending&&!this.options.optOut&&this._unsentCount()>0){this._sending=true;var url=("https:"===window.location.protocol?"https":"http")+"://"+this.options.apiEndpoint+"/";var numEvents=Math.min(this._unsentCount(),this.options.uploadBatchSize);var mergedEvents=this._mergeEventsAndIdentifys(numEvents);var maxEventId=mergedEvents.maxEventId;var maxIdentifyId=mergedEvents.maxIdentifyId;var events=JSON.stringify(mergedEvents.eventsToSend);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 scope=this;new Request(url,data).send(function(status,response){scope._sending=false;try{if(status===200&&response==="success"){scope.removeEvents(maxEventId,maxIdentifyId);if(scope.options.saveEvents){scope.saveEvents()}if(!scope._sendEventsIfReady(callback)&&callback){callback(status,response)}}else if(status===413){if(scope.options.uploadBatchSize===1){scope.removeEvents(maxEventId,maxIdentifyId)}scope.options.uploadBatchSize=Math.ceil(numEvents/2);scope.sendEvents(callback)}else if(callback){callback(status,response)}}catch(e){}})}else if(callback){callback(0,"No request sent")}};Amplitude.prototype._mergeEventsAndIdentifys=function(numEvents){var eventsToSend=[];var eventIndex=0;var maxEventId=-1;var identifyIndex=0;var maxIdentifyId=-1;while(eventsToSend.length=this._unsentIdentifys.length){event=this._unsentEvents[eventIndex++];maxEventId=event.event_id}else if(eventIndex>=this._unsentEvents.length){event=this._unsentIdentifys[identifyIndex++];maxIdentifyId=event.event_id}else{if(!("sequence_number"in this._unsentEvents[eventIndex])||this._unsentEvents[eventIndex].sequence_number>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":18}],18:[function(require,module,exports){var UTF8={encode:function(s){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 json=window.JSON||{};var stringify=json.stringify;var parse=json.parse;module.exports=parse&&stringify?JSON:require("json-fallback")},{"json-fallback":19}],19:[function(require,module,exports){(function(){"use strict";var JSON=module.exports={};function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()}}var cx,escapable,gap,indent,meta,rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i>16)+(y>>16)+(lsw>>16);return msw<<16|lsw&65535}function bit_rol(num,cnt){return num<>>32-cnt}function md5_cmn(q,a,b,x,s,t){return safe_add(bit_rol(safe_add(safe_add(a,q),safe_add(x,t)),s),b)}function md5_ff(a,b,c,d,x,s,t){return md5_cmn(b&c|~b&d,a,b,x,s,t)}function md5_gg(a,b,c,d,x,s,t){return md5_cmn(b&d|c&~d,a,b,x,s,t)}function md5_hh(a,b,c,d,x,s,t){return md5_cmn(b^c^d,a,b,x,s,t)}function md5_ii(a,b,c,d,x,s,t){return md5_cmn(c^(b|~d),a,b,x,s,t)}function binl_md5(x,len){x[len>>5]|=128<>>9<<4)+14]=len;var i,olda,oldb,oldc,oldd,a=1732584193,b=-271733879,c=-1732584194,d=271733878;for(i=0;i>5]>>>i%32&255)}return output}function rstr2binl(input){var i,output=[];output[(input.length>>2)-1]=undefined;for(i=0;i>5]|=(input.charCodeAt(i/8)&255)<16){bkey=binl_md5(bkey,key.length*8)}for(i=0;i<16;i+=1){ipad[i]=bkey[i]^909522486;opad[i]=bkey[i]^1549556828}hash=binl_md5(ipad.concat(rstr2binl(data)),512+data.length*8);return binl2rstr(binl_md5(opad.concat(hash),512+128))}function rstr2hex(input){var hex_tab="0123456789abcdef",output="",x,i;for(i=0;i>>4&15)+hex_tab.charAt(x&15)}return output}function str2rstr_utf8(input){return unescape(encodeURIComponent(input))}function raw_md5(s){return rstr_md5(str2rstr_utf8(s))}function hex_md5(s){return rstr2hex(raw_md5(s))}function raw_hmac_md5(k,d){return rstr_hmac_md5(str2rstr_utf8(k),str2rstr_utf8(d))}function hex_hmac_md5(k,d){return rstr2hex(raw_hmac_md5(k,d))}function md5(string,key,raw){if(!key){if(!raw){return hex_md5(string)}return raw_md5(string)}if(!raw){return hex_hmac_md5(key,string)}return raw_hmac_md5(key,string)}if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports){exports=module.exports=md5}exports.md5=md5}else{if(typeof define==="function"&&define.amd){define(function(){return md5})}else{$.md5=md5}}})(this)},{}],8:[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)}},{}],9:[function(require,module,exports){var querystring=require("querystring");var Request=function(url,data){this.url=url;this.data=data||{}};Request.prototype.send=function(callback){var isIE=window.XDomainRequest?true:false;if(isIE){var xdr=new window.XDomainRequest;xdr.open("POST",this.url,true);xdr.onload=function(){callback(200,xdr.responseText)};xdr.onerror=function(){if(xdr.responseText==="Request Entity Too Large"){callback(413,xdr.responseText)}else{callback(500,xdr.responseText)}};xdr.ontimeout=function(){};xdr.onprogress=function(){};xdr.send(querystring.stringify(this.data))}else{var xhr=new XMLHttpRequest;xhr.open("POST",this.url,true);xhr.onreadystatechange=function(){if(xhr.readyState===4){callback(xhr.status,xhr.responseText)}};xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");xhr.send(querystring.stringify(this.data))}};module.exports=Request},{querystring:21}],21:[function(require,module,exports){var encode=encodeURIComponent;var decode=decodeURIComponent;var trim=require("trim");var type=require("type");exports.parse=function(str){if("string"!=typeof str)return{};str=trim(str);if(""==str)return{};if("?"==str.charAt(0))str=str.slice(1);var obj={};var pairs=str.split("&");for(var i=0;i0){if(q.length==2){if(typeof q[1]==FUNC_TYPE){result[q[0]]=q[1].call(this,match)}else{result[q[0]]=q[1]}}else if(q.length==3){if(typeof q[1]===FUNC_TYPE&&!(q[1].exec&&q[1].test)){result[q[0]]=match?q[1].call(this,match,q[2]):undefined}else{result[q[0]]=match?match.replace(q[1],q[2]):undefined}}else if(q.length==4){result[q[0]]=match?q[3].call(this,match.replace(q[1],q[2])):undefined}}else{result[q]=match?match:undefined}}}}i+=2}return result},str:function(str,map){for(var i in map){if(typeof map[i]===OBJ_TYPE&&map[i].length>0){for(var j=0;j>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,uuid)};module.exports=uuid},{}],12:[function(require,module,exports){module.exports="2.7.0"},{}],13:[function(require,module,exports){var type=require("./type");var AMP_OP_ADD="$add";var AMP_OP_APPEND="$append";var AMP_OP_SET="$set";var AMP_OP_SET_ONCE="$setOnce";var AMP_OP_UNSET="$unset";var log=function(s){console.log("[Amplitude] "+s)};var Identify=function(){this.userPropertiesOperations={};this.properties=[]};Identify.prototype.add=function(property,value){if(type(value)==="number"||type(value)==="string"){this._addOperation(AMP_OP_ADD,property,value)}else{log("Unsupported type for value: "+type(value)+", expecting number or string")}return this};Identify.prototype.append=function(property,value){this._addOperation(AMP_OP_APPEND,property,value);return this};Identify.prototype.set=function(property,value){this._addOperation(AMP_OP_SET,property,value);return this};Identify.prototype.setOnce=function(property,value){this._addOperation(AMP_OP_SET_ONCE,property,value);return this};Identify.prototype.unset=function(property){this._addOperation(AMP_OP_UNSET,property,"-");return this};Identify.prototype._addOperation=function(operation,property,value){if(this.properties.indexOf(property)!==-1){log('User property "'+property+'" already used in this identify, skipping operation '+operation);return}if(!(operation in this.userPropertiesOperations)){this.userPropertiesOperations[operation]={}}this.userPropertiesOperations[operation][property]=value;this.properties.push(property)};module.exports=Identify},{"./type":14}],14:[function(require,module,exports){var toString=Object.prototype.toString;module.exports=function(val){switch(toString.call(val)){case"[object Date]":return"date";case"[object RegExp]":return"regexp";case"[object Arguments]":return"arguments";case"[object Array]":return"array";case"[object Error]":return"error"}if(val===null){return"null"}if(val===undefined){return"undefined"}if(val!==val){return"nan"}if(val&&val.nodeType===1){return"element"}if(typeof Buffer!=="undefined"&&Buffer.isBuffer(val)){return"buffer"}val=val.valueOf?val.valueOf():Object.prototype.valueOf.apply(val);return typeof val}},{}]},{},{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 instance=new Amplitude;instance._q=old._q||[];module.exports=instance},{"./amplitude":2}],2:[function(require,module,exports){var cookieStorage=require("./cookiestorage");var JSON=require("json");var language=require("./language");var localStorage=require("./localstorage");var md5=require("JavaScript-MD5");var object=require("object");var Request=require("./xhr");var UAParser=require("ua-parser-js");var UUID=require("./uuid");var version=require("./version");var Identify=require("./identify");var type=require("./type");var log=function(s){console.log("[Amplitude] "+s)};var IDENTIFY_EVENT="$identify";var API_VERSION=2;var MAX_STRING_LENGTH=1024;var DEFAULT_OPTIONS={apiEndpoint:"api.amplitude.com",cookieExpiration:365*10,cookieName:"amplitude_id",domain:undefined,includeUtm:false,language:language.language,optOut:false,platform:"Web",savedMaxCount:1e3,saveEvents:true,sessionTimeout:30*60*1e3,unsentKey:"amplitude_unsent",unsentIdentifyKey:"amplitude_unsent_identify",uploadBatchSize:100,batchEvents:false,eventUploadThreshold:30,eventUploadPeriodMillis:30*1e3};var LocalStorageKeys={LAST_EVENT_ID:"amplitude_lastEventId",LAST_IDENTIFY_ID:"amplitude_lastIdentifyId",LAST_SEQUENCE_NUMBER:"amplitude_lastSequenceNumber",LAST_EVENT_TIME:"amplitude_lastEventTime",SESSION_ID:"amplitude_sessionId",REFERRER:"amplitude_referrer",DEVICE_ID:"amplitude_deviceId",USER_ID:"amplitude_userId",OPT_OUT:"amplitude_optOut"};var Amplitude=function(){this._unsentEvents=[];this._unsentIdentifys=[];this._ua=new UAParser(navigator.userAgent).getResult();this.options=object.merge({},DEFAULT_OPTIONS);this.cookieStorage=(new cookieStorage).getStorage();this._q=[]};Amplitude.prototype._eventId=0;Amplitude.prototype._identifyId=0;Amplitude.prototype._sequenceNumber=0;Amplitude.prototype._sending=false;Amplitude.prototype._lastEventTime=null;Amplitude.prototype._sessionId=null;Amplitude.prototype._newSession=false;Amplitude.prototype._updateScheduled=false;Amplitude.prototype.Identify=Identify;Amplitude.prototype.init=function(apiKey,opt_userId,opt_config,callback){try{this.options.apiKey=apiKey;if(opt_config){if(opt_config.saveEvents!==undefined){this.options.saveEvents=!!opt_config.saveEvents}if(opt_config.domain!==undefined){this.options.domain=opt_config.domain}if(opt_config.includeUtm!==undefined){this.options.includeUtm=!!opt_config.includeUtm}if(opt_config.includeReferrer!==undefined){this.options.includeReferrer=!!opt_config.includeReferrer}if(opt_config.batchEvents!==undefined){this.options.batchEvents=!!opt_config.batchEvents}this.options.platform=opt_config.platform||this.options.platform;this.options.language=opt_config.language||this.options.language;this.options.sessionTimeout=opt_config.sessionTimeout||this.options.sessionTimeout;this.options.uploadBatchSize=opt_config.uploadBatchSize||this.options.uploadBatchSize;this.options.eventUploadThreshold=opt_config.eventUploadThreshold||this.options.eventUploadThreshold;this.options.savedMaxCount=opt_config.savedMaxCount||this.options.savedMaxCount;this.options.eventUploadPeriodMillis=opt_config.eventUploadPeriodMillis||this.options.eventUploadPeriodMillis}this.cookieStorage.options({expirationDays:this.options.cookieExpiration,domain:this.options.domain});this.options.domain=this.cookieStorage.options().domain;_migrateLocalStorageDataToCookie(this);_loadCookieData(this);this.options.deviceId=opt_config&&opt_config.deviceId!==undefined&&opt_config.deviceId!==null&&opt_config.deviceId||this.options.deviceId||UUID();this.options.userId=opt_userId!==undefined&&opt_userId!==null&&opt_userId||this.options.userId||null;this._lastEventTime=this._lastEventTime||parseInt(localStorage.getItem(LocalStorageKeys.LAST_EVENT_TIME))||null;this._sessionId=this._sessionId||parseInt(localStorage.getItem(LocalStorageKeys.SESSION_ID))||null;this._eventId=this._eventId||parseInt(localStorage.getItem(LocalStorageKeys.LAST_EVENT_ID))||0;this._identifyId=this._identifyId||parseInt(localStorage.getItem(LocalStorageKeys.LAST_IDENTIFY_ID))||0;this._sequenceNumber=this._sequenceNumber||parseInt(localStorage.getItem(LocalStorageKeys.LAST_SEQUENCE_NUMBER))||0;var now=(new Date).getTime();if(!this._sessionId||!this._lastEventTime||now-this._lastEventTime>this.options.sessionTimeout){this._newSession=true;this._sessionId=now}this._lastEventTime=now;_saveCookieData(this);_clearSessionAndEventTrackingFromLocalStorage();if(this.options.saveEvents){this._loadSavedUnsentEvents(this.options.unsentKey,"_unsentEvents");this._loadSavedUnsentEvents(this.options.unsentIdentifyKey,"_unsentIdentifys")}this._sendEventsIfReady();if(this.options.includeUtm){this._initUtmData()}if(this.options.includeReferrer){this._saveReferrer(this._getReferrer())}}catch(e){log(e)}if(callback&&type(callback)==="function"){callback()}};Amplitude.prototype.runQueuedFunctions=function(){for(var i=0;i=this.options.eventUploadThreshold){this.sendEvents(callback);return true}if(!this._updateScheduled){this._updateScheduled=true;setTimeout(function(){this._updateScheduled=false;this.sendEvents()}.bind(this),this.options.eventUploadPeriodMillis)}return false};var _migrateLocalStorageDataToCookie=function(scope){var cookieData=scope.cookieStorage.get(scope.options.cookieName);if(cookieData&&cookieData.deviceId){return}var cookieDeviceId=cookieData&&cookieData.deviceId||null;var cookieUserId=cookieData&&cookieData.userId||null;var cookieOptOut=cookieData&&cookieData.optOut!==null&&cookieData.optOut!==undefined?cookieData.optOut:null;var keySuffix="_"+scope.options.apiKey.slice(0,6);var localStorageDeviceId=localStorage.getItem(LocalStorageKeys.DEVICE_ID+keySuffix);if(localStorageDeviceId){localStorage.removeItem(LocalStorageKeys.DEVICE_ID+keySuffix)}var localStorageUserId=localStorage.getItem(LocalStorageKeys.USER_ID+keySuffix);if(localStorageUserId){localStorage.removeItem(LocalStorageKeys.USER_ID+keySuffix)}var localStorageOptOut=localStorage.getItem(LocalStorageKeys.OPT_OUT+keySuffix);if(localStorageOptOut!==null&&localStorageOptOut!==undefined){localStorage.removeItem(LocalStorageKeys.OPT_OUT+keySuffix);localStorageOptOut=String(localStorageOptOut)==="true"}scope.cookieStorage.set(scope.options.cookieName,{deviceId:cookieDeviceId||localStorageDeviceId,userId:cookieUserId||localStorageUserId,optOut:cookieOptOut!==undefined&&cookieOptOut!==null?cookieOptOut:localStorageOptOut})};var _loadCookieData=function(scope){var cookieData=scope.cookieStorage.get(scope.options.cookieName);if(cookieData){if(cookieData.deviceId){scope.options.deviceId=cookieData.deviceId}if(cookieData.userId){scope.options.userId=cookieData.userId}if(cookieData.optOut!==null&&cookieData.optOut!==undefined){scope.options.optOut=cookieData.optOut}if(cookieData.sessionId){scope._sessionId=parseInt(cookieData.sessionId)}if(cookieData.lastEventTime){scope._lastEventTime=parseInt(cookieData.lastEventTime)}if(cookieData.eventId){scope._eventId=parseInt(cookieData.eventId)}if(cookieData.identifyId){scope._identifyId=parseInt(cookieData.identifyId)}if(cookieData.sequenceNumber){scope._sequenceNumber=parseInt(cookieData.sequenceNumber)}}};var _saveCookieData=function(scope){scope.cookieStorage.set(scope.options.cookieName,{deviceId:scope.options.deviceId,userId:scope.options.userId,optOut:scope.options.optOut,sessionId:scope._sessionId,lastEventTime:scope._lastEventTime,eventId:scope._eventId,identifyId:scope._identifyId,sequenceNumber:scope._sequenceNumber})};var _clearSessionAndEventTrackingFromLocalStorage=function(){localStorage.removeItem(LocalStorageKeys.SESSION_ID);localStorage.removeItem(LocalStorageKeys.LAST_EVENT_TIME);localStorage.removeItem(LocalStorageKeys.LAST_EVENT_ID);localStorage.removeItem(LocalStorageKeys.LAST_IDENTIFY_ID);localStorage.removeItem(LocalStorageKeys.LAST_SEQUENCE_NUMBER)};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._initUtmData=function(queryParams,cookieParams){queryParams=queryParams||location.search;cookieParams=cookieParams||this.cookieStorage.get("__utmz");this._utmProperties=Amplitude._getUtmData(cookieParams,queryParams)};Amplitude.prototype._getReferrer=function(){return document.referrer};Amplitude.prototype._getReferringDomain=function(referrer){if(referrer===null||referrer===undefined||referrer===""){return null}var parts=referrer.split("/");if(parts.length>=3){return parts[2]}return null};Amplitude.prototype._saveReferrer=function(referrer){if(referrer===null||referrer===undefined||referrer===""){return}var referring_domain=this._getReferringDomain(referrer);var identify=(new Identify).setOnce("initial_referrer",referrer);identify.setOnce("initial_referring_domain",referring_domain);var hasSessionStorage=window.sessionStorage?true:false;if(hasSessionStorage&&!window.sessionStorage.getItem(LocalStorageKeys.REFERRER)||!hasSessionStorage){identify.set("referrer",referrer).set("referring_domain",referring_domain);if(hasSessionStorage){window.sessionStorage.setItem(LocalStorageKeys.REFERRER,referrer)}}this.identify(identify)};Amplitude.prototype.saveEvents=function(){try{localStorage.setItem(this.options.unsentKey,JSON.stringify(this._unsentEvents));localStorage.setItem(this.options.unsentIdentifyKey,JSON.stringify(this._unsentIdentifys))}catch(e){}};Amplitude.prototype.setDomain=function(domain){try{this.cookieStorage.options({domain:domain});this.options.domain=this.cookieStorage.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.setOptOut=function(enable){try{this.options.optOut=enable;_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){var identify=new Identify;for(var property in userProperties){if(userProperties.hasOwnProperty(property)){identify.set(property,userProperties[property])}}this.identify(identify)};Amplitude.prototype.identify=function(identify){if(type(identify)==="object"&&"_q"in identify){var instance=new Identify;for(var i=0;i0){this._logEvent(IDENTIFY_EVENT,null,null,identify.userPropertiesOperations)}};Amplitude.prototype.setVersionName=function(versionName){try{this.options.versionName=versionName}catch(e){log(e)}};Amplitude.prototype._truncate=function(value){if(type(value)==="array"){for(var i=0;iMAX_STRING_LENGTH?value.substring(0,MAX_STRING_LENGTH):value}return value};Amplitude.prototype._logEvent=function(eventType,eventProperties,apiProperties,userProperties,callback){if(type(callback)!=="function"){callback=null}if(!eventType||this.options.optOut){if(callback){callback(0,"No request sent")}return}try{var eventId;if(eventType===IDENTIFY_EVENT){eventId=this.nextIdentifyId()}else{eventId=this.nextEventId()}var sequenceNumber=this.nextSequenceNumber();var eventTime=(new Date).getTime();var ua=this._ua;if(!this._sessionId||!this._lastEventTime||eventTime-this._lastEventTime>this.options.sessionTimeout){this._sessionId=eventTime}this._lastEventTime=eventTime;_saveCookieData(this);userProperties=userProperties||{};if(eventType!==IDENTIFY_EVENT){object.merge(userProperties,this._utmProperties)}apiProperties=apiProperties||{};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.name||null,os_version:ua.browser.major||null,device_model:ua.os.name||null,language:this.options.language,api_properties:apiProperties,event_properties:this._truncate(eventProperties),user_properties:this._truncate(userProperties),uuid:UUID(),library:{name:"amplitude-js",version:this.__VERSION__},sequence_number:sequenceNumber};if(eventType===IDENTIFY_EVENT){this._unsentIdentifys.push(event);this._limitEventsQueued(this._unsentIdentifys)}else{this._unsentEvents.push(event);this._limitEventsQueued(this._unsentEvents)}if(this.options.saveEvents){this.saveEvents()}if(!this._sendEventsIfReady(callback)&&callback){callback(0,"No request sent")}return eventId}catch(e){log(e)}};Amplitude.prototype._limitEventsQueued=function(queue){if(queue.length>this.options.savedMaxCount){queue.splice(0,queue.length-this.options.savedMaxCount)}};Amplitude.prototype.logEvent=function(eventType,eventProperties,callback){return this._logEvent(eventType,eventProperties,null,null,callback)};var _isNumber=function(n){return!isNaN(parseFloat(n))&&isFinite(n)};Amplitude.prototype.logRevenue=function(price,quantity,product){if(!_isNumber(price)||quantity!==undefined&&!_isNumber(quantity)){return}return this._logEvent("revenue_amount",{},{productId:product,special:"revenue_amount",quantity:quantity||1,price:price})};Amplitude.prototype.removeEvents=function(maxEventId,maxIdentifyId){if(maxEventId>=0){var filteredEvents=[];for(var i=0;imaxEventId){filteredEvents.push(this._unsentEvents[i])}}this._unsentEvents=filteredEvents}if(maxIdentifyId>=0){var filteredIdentifys=[];for(var j=0;jmaxIdentifyId){filteredIdentifys.push(this._unsentIdentifys[j])}}this._unsentIdentifys=filteredIdentifys}};Amplitude.prototype.sendEvents=function(callback){if(!this._sending&&!this.options.optOut&&this._unsentCount()>0){this._sending=true;var url=("https:"===window.location.protocol?"https":"http")+"://"+this.options.apiEndpoint+"/";var numEvents=Math.min(this._unsentCount(),this.options.uploadBatchSize);var mergedEvents=this._mergeEventsAndIdentifys(numEvents);var maxEventId=mergedEvents.maxEventId;var maxIdentifyId=mergedEvents.maxIdentifyId;var events=JSON.stringify(mergedEvents.eventsToSend);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 scope=this;new Request(url,data).send(function(status,response){scope._sending=false;try{if(status===200&&response==="success"){scope.removeEvents(maxEventId,maxIdentifyId);if(scope.options.saveEvents){scope.saveEvents()}if(!scope._sendEventsIfReady(callback)&&callback){callback(status,response)}}else if(status===413){if(scope.options.uploadBatchSize===1){scope.removeEvents(maxEventId,maxIdentifyId)}scope.options.uploadBatchSize=Math.ceil(numEvents/2);scope.sendEvents(callback)}else if(callback){callback(status,response)}}catch(e){}})}else if(callback){callback(0,"No request sent")}};Amplitude.prototype._mergeEventsAndIdentifys=function(numEvents){var eventsToSend=[];var eventIndex=0;var maxEventId=-1;var identifyIndex=0;var maxIdentifyId=-1;while(eventsToSend.length=this._unsentIdentifys.length){event=this._unsentEvents[eventIndex++];maxEventId=event.event_id}else if(eventIndex>=this._unsentEvents.length){event=this._unsentIdentifys[identifyIndex++];maxIdentifyId=event.event_id}else{if(!("sequence_number"in this._unsentEvents[eventIndex])||this._unsentEvents[eventIndex].sequence_number>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":18}],18:[function(require,module,exports){var UTF8={encode:function(s){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 json=window.JSON||{};var stringify=json.stringify;var parse=json.parse;module.exports=parse&&stringify?JSON:require("json-fallback")},{"json-fallback":19}],19:[function(require,module,exports){(function(){"use strict";var JSON=module.exports={};function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()}}var cx,escapable,gap,indent,meta,rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i>16)+(y>>16)+(lsw>>16);return msw<<16|lsw&65535}function bit_rol(num,cnt){return num<>>32-cnt}function md5_cmn(q,a,b,x,s,t){return safe_add(bit_rol(safe_add(safe_add(a,q),safe_add(x,t)),s),b)}function md5_ff(a,b,c,d,x,s,t){return md5_cmn(b&c|~b&d,a,b,x,s,t)}function md5_gg(a,b,c,d,x,s,t){ +return md5_cmn(b&d|c&~d,a,b,x,s,t)}function md5_hh(a,b,c,d,x,s,t){return md5_cmn(b^c^d,a,b,x,s,t)}function md5_ii(a,b,c,d,x,s,t){return md5_cmn(c^(b|~d),a,b,x,s,t)}function binl_md5(x,len){x[len>>5]|=128<>>9<<4)+14]=len;var i,olda,oldb,oldc,oldd,a=1732584193,b=-271733879,c=-1732584194,d=271733878;for(i=0;i>5]>>>i%32&255)}return output}function rstr2binl(input){var i,output=[];output[(input.length>>2)-1]=undefined;for(i=0;i>5]|=(input.charCodeAt(i/8)&255)<16){bkey=binl_md5(bkey,key.length*8)}for(i=0;i<16;i+=1){ipad[i]=bkey[i]^909522486;opad[i]=bkey[i]^1549556828}hash=binl_md5(ipad.concat(rstr2binl(data)),512+data.length*8);return binl2rstr(binl_md5(opad.concat(hash),512+128))}function rstr2hex(input){var hex_tab="0123456789abcdef",output="",x,i;for(i=0;i>>4&15)+hex_tab.charAt(x&15)}return output}function str2rstr_utf8(input){return unescape(encodeURIComponent(input))}function raw_md5(s){return rstr_md5(str2rstr_utf8(s))}function hex_md5(s){return rstr2hex(raw_md5(s))}function raw_hmac_md5(k,d){return rstr_hmac_md5(str2rstr_utf8(k),str2rstr_utf8(d))}function hex_hmac_md5(k,d){return rstr2hex(raw_hmac_md5(k,d))}function md5(string,key,raw){if(!key){if(!raw){return hex_md5(string)}return raw_md5(string)}if(!raw){return hex_hmac_md5(key,string)}return raw_hmac_md5(key,string)}if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports){exports=module.exports=md5}exports.md5=md5}else{if(typeof define==="function"&&define.amd){define(function(){return md5})}else{$.md5=md5}}})(this)},{}],8:[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)}},{}],9:[function(require,module,exports){var querystring=require("querystring");var Request=function(url,data){this.url=url;this.data=data||{}};Request.prototype.send=function(callback){var isIE=window.XDomainRequest?true:false;if(isIE){var xdr=new window.XDomainRequest;xdr.open("POST",this.url,true);xdr.onload=function(){callback(200,xdr.responseText)};xdr.onerror=function(){if(xdr.responseText==="Request Entity Too Large"){callback(413,xdr.responseText)}else{callback(500,xdr.responseText)}};xdr.ontimeout=function(){};xdr.onprogress=function(){};xdr.send(querystring.stringify(this.data))}else{var xhr=new XMLHttpRequest;xhr.open("POST",this.url,true);xhr.onreadystatechange=function(){if(xhr.readyState===4){callback(xhr.status,xhr.responseText)}};xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");xhr.send(querystring.stringify(this.data))}};module.exports=Request},{querystring:21}],21:[function(require,module,exports){var encode=encodeURIComponent;var decode=decodeURIComponent;var trim=require("trim");var type=require("type");exports.parse=function(str){if("string"!=typeof str)return{};str=trim(str);if(""==str)return{};if("?"==str.charAt(0))str=str.slice(1);var obj={};var pairs=str.split("&");for(var i=0;i0){if(q.length==2){if(typeof q[1]==FUNC_TYPE){result[q[0]]=q[1].call(this,match)}else{result[q[0]]=q[1]}}else if(q.length==3){if(typeof q[1]===FUNC_TYPE&&!(q[1].exec&&q[1].test)){result[q[0]]=match?q[1].call(this,match,q[2]):undefined}else{result[q[0]]=match?match.replace(q[1],q[2]):undefined}}else if(q.length==4){result[q[0]]=match?q[3].call(this,match.replace(q[1],q[2])):undefined}}else{result[q]=match?match:undefined}}}}i+=2}return result},str:function(str,map){for(var i in map){if(typeof map[i]===OBJ_TYPE&&map[i].length>0){for(var j=0;j>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,uuid)};module.exports=uuid},{}],12:[function(require,module,exports){module.exports="2.7.0"},{}],13:[function(require,module,exports){var type=require("./type");var AMP_OP_ADD="$add";var AMP_OP_APPEND="$append";var AMP_OP_SET="$set";var AMP_OP_SET_ONCE="$setOnce";var AMP_OP_UNSET="$unset";var log=function(s){console.log("[Amplitude] "+s)};var Identify=function(){this.userPropertiesOperations={};this.properties=[]};Identify.prototype.add=function(property,value){if(type(value)==="number"||type(value)==="string"){this._addOperation(AMP_OP_ADD,property,value)}else{log("Unsupported type for value: "+type(value)+", expecting number or string")}return this};Identify.prototype.append=function(property,value){this._addOperation(AMP_OP_APPEND,property,value);return this};Identify.prototype.set=function(property,value){this._addOperation(AMP_OP_SET,property,value);return this};Identify.prototype.setOnce=function(property,value){this._addOperation(AMP_OP_SET_ONCE,property,value);return this};Identify.prototype.unset=function(property){this._addOperation(AMP_OP_UNSET,property,"-");return this};Identify.prototype._addOperation=function(operation,property,value){if(this.properties.indexOf(property)!==-1){log('User property "'+property+'" already used in this identify, skipping operation '+operation);return}if(!(operation in this.userPropertiesOperations)){this.userPropertiesOperations[operation]={}}this.userPropertiesOperations[operation][property]=value;this.properties.push(property)};module.exports=Identify},{"./type":14}],14:[function(require,module,exports){var toString=Object.prototype.toString;module.exports=function(val){switch(toString.call(val)){case"[object Date]":return"date";case"[object RegExp]":return"regexp";case"[object Arguments]":return"arguments";case"[object Array]":return"array";case"[object Error]":return"error"}if(val===null){return"null"}if(val===undefined){return"undefined"}if(val!==val){return"nan"}if(val&&val.nodeType===1){return"element"}if(typeof Buffer!=="undefined"&&Buffer.isBuffer(val)){return"buffer"}val=val.valueOf?val.valueOf():Object.prototype.valueOf.apply(val);return typeof val}},{}]},{},{1:""})); \ No newline at end of file diff --git a/src/amplitude.js b/src/amplitude.js index eda17ed5..39ddb677 100644 --- a/src/amplitude.js +++ b/src/amplitude.js @@ -43,7 +43,9 @@ var LocalStorageKeys = { LAST_SEQUENCE_NUMBER: 'amplitude_lastSequenceNumber', LAST_EVENT_TIME: 'amplitude_lastEventTime', SESSION_ID: 'amplitude_sessionId', + REFERRER: 'amplitude_referrer', + // Used in cookie as well DEVICE_ID: 'amplitude_deviceId', USER_ID: 'amplitude_userId', OPT_OUT: 'amplitude_optOut' @@ -150,6 +152,10 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config, callback) { if (this.options.includeUtm) { this._initUtmData(); } + + if (this.options.includeReferrer) { + this._saveReferrer(this._getReferrer()); + } } catch (e) { log(e); } @@ -361,12 +367,39 @@ Amplitude.prototype._getReferrer = function() { return document.referrer; }; -Amplitude.prototype._getReferringDomain = function() { - var parts = this._getReferrer().split("/"); +Amplitude.prototype._getReferringDomain = function(referrer) { + if (referrer === null || referrer === undefined || referrer === '') { + return null; + } + var parts = referrer.split('/'); if (parts.length >= 3) { return parts[2]; } - return ""; + return null; +}; + +// since user properties are propagated on the server, only send once per session, don't need to send with every event +Amplitude.prototype._saveReferrer = function(referrer) { + if (referrer === null || referrer === undefined || referrer === '') { + return; + } + + // always setOnce initial referrer + var referring_domain = this._getReferringDomain(referrer); + var identify = new Identify().setOnce('initial_referrer', referrer); + identify.setOnce('initial_referring_domain', referring_domain); + + // only save referrer if not already in session storage or if storage disabled + var hasSessionStorage = window.sessionStorage ? true : false; + if ((hasSessionStorage && !window.sessionStorage.getItem(LocalStorageKeys.REFERRER)) || !hasSessionStorage) { + identify.set('referrer', referrer).set('referring_domain', referring_domain); + + if (hasSessionStorage) { + window.sessionStorage.setItem(LocalStorageKeys.REFERRER, referrer); + } + } + + this.identify(identify); }; Amplitude.prototype.saveEvents = function() { @@ -522,14 +555,6 @@ Amplitude.prototype._logEvent = function(eventType, eventProperties, apiProperti // Only add utm properties to user properties for events if (eventType !== IDENTIFY_EVENT) { object.merge(userProperties, this._utmProperties); - - // Add referral info onto the user properties - if (this.options.includeReferrer) { - object.merge(userProperties, { - 'referrer': this._getReferrer(), - 'referring_domain': this._getReferringDomain() - }); - } } apiProperties = apiProperties || {}; diff --git a/test/amplitude.js b/test/amplitude.js index 44bf4140..181ac4dd 100644 --- a/test/amplitude.js +++ b/test/amplitude.js @@ -27,6 +27,7 @@ describe('Amplitude', function() { function reset() { localStorage.clear(); + sessionStorage.clear(); cookie.remove(amplitude.options.cookieName); cookie.reset(); } @@ -1500,41 +1501,92 @@ describe('Amplitude', function() { assert.equal(events[0].user_properties.referring_domain, undefined); }); - it('should send referrer data when the includeReferrer flag is true', function() { + it('should only send referrer via identify call when the includeReferrer flag is true', function() { reset(); - amplitude.init(apiKey, undefined, {includeReferrer: true}); - + amplitude.init(apiKey, undefined, {includeReferrer: true, batchEvents: true, eventUploadThreshold: 2}); amplitude.logEvent('Referrer Test Event', {}); assert.lengthOf(server.requests, 1); var events = JSON.parse(querystring.parse(server.requests[0].requestBody).e); + assert.lengthOf(events, 2); + + // first event should be identify with initial_referrer and referrer + assert.equal(events[0].event_type, '$identify'); assert.deepEqual(events[0].user_properties, { - referrer: 'https://amplitude.com/contact', - referring_domain: 'amplitude.com' + '$set': { + 'referrer': 'https://amplitude.com/contact', + 'referring_domain': 'amplitude.com' + }, + '$setOnce': { + 'initial_referrer': 'https://amplitude.com/contact', + 'initial_referring_domain': 'amplitude.com' + } }); + + // second event should be the test event with no referrer information + assert.equal(events[1].event_type, 'Referrer Test Event'); + assert.deepEqual(events[1].user_properties, {}); + + // referrer should be propagated to session storage + assert.equal(sessionStorage.getItem('amplitude_referrer'), 'https://amplitude.com/contact'); }); - it('should add referrer data to the user properties on events only', function() { + it('should not set referrer if referrer data already in session storage', function() { reset(); - amplitude.init(apiKey, undefined, {includeReferrer: true}); - - amplitude.setUserProperties({user_prop: true}); + sessionStorage.setItem('amplitude_referrer', 'https://www.google.com/search?'); + amplitude.init(apiKey, undefined, {includeReferrer: true, batchEvents: true, eventUploadThreshold: 2}); + amplitude.logEvent('Referrer Test Event', {}); assert.lengthOf(server.requests, 1); var events = JSON.parse(querystring.parse(server.requests[0].requestBody).e); + assert.lengthOf(events, 2); + + // first event should be identify with initial_referrer and NO referrer + assert.equal(events[0].event_type, '$identify'); assert.deepEqual(events[0].user_properties, { - $set: { - 'user_prop': true + '$setOnce': { + 'initial_referrer': 'https://amplitude.com/contact', + 'initial_referring_domain': 'amplitude.com' } }); - server.respondWith('success'); - server.respond(); - amplitude.logEvent('Referrer test event'); - assert.lengthOf(server.requests, 2); - var events = JSON.parse(querystring.parse(server.requests[1].requestBody).e); + // second event should be the test event with no referrer information + assert.equal(events[1].event_type, 'Referrer Test Event'); + assert.deepEqual(events[1].user_properties, {}); + }); + + it('should not override any existing initial referrer values in session storage', function() { + reset(); + sessionStorage.setItem('amplitude_referrer', 'https://www.google.com/search?'); + amplitude.init(apiKey, undefined, {includeReferrer: true, batchEvents: true, eventUploadThreshold: 3}); + amplitude._saveReferrer('https://facebook.com/contact'); + amplitude.logEvent('Referrer Test Event', {}); + assert.lengthOf(server.requests, 1); + var events = JSON.parse(querystring.parse(server.requests[0].requestBody).e); + assert.lengthOf(events, 3); + + // first event should be identify with initial_referrer and NO referrer + assert.equal(events[0].event_type, '$identify'); assert.deepEqual(events[0].user_properties, { - referrer: 'https://amplitude.com/contact', - referring_domain: 'amplitude.com' + '$setOnce': { + 'initial_referrer': 'https://amplitude.com/contact', + 'initial_referring_domain': 'amplitude.com' + } + }); + + // second event should be another identify but with the new referrer + assert.equal(events[1].event_type, '$identify'); + assert.deepEqual(events[1].user_properties, { + '$setOnce': { + 'initial_referrer': 'https://facebook.com/contact', + 'initial_referring_domain': 'facebook.com' + } }); + + // third event should be the test event with no referrer information + assert.equal(events[2].event_type, 'Referrer Test Event'); + assert.deepEqual(events[2].user_properties, {}); + + // existing value persists + assert.equal(sessionStorage.getItem('amplitude_referrer'), 'https://www.google.com/search?'); }); }); diff --git a/test/browser/amplitudejs.html b/test/browser/amplitudejs.html index 708c15a4..f848c2d4 100644 --- a/test/browser/amplitudejs.html +++ b/test/browser/amplitudejs.html @@ -70,7 +70,7 @@ } + + +

Amplitude JS Test

+