amplitude-js
Version:
Javascript library for Amplitude Analytics
1,696 lines (1,411 loc) • 121 kB
JavaScript
import _objectSpread from '@babel/runtime/helpers/objectSpread';
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import _typeof from '@babel/runtime/helpers/typeof';
import _classCallCheck from '@babel/runtime/helpers/classCallCheck';
import _createClass from '@babel/runtime/helpers/createClass';
import md5 from 'blueimp-md5';
import queryString from 'query-string';
import UAParser from '@amplitude/ua-parser-js';
var Constants = {
DEFAULT_INSTANCE: '$default_instance',
API_VERSION: 2,
MAX_STRING_LENGTH: 4096,
MAX_PROPERTY_KEYS: 1000,
IDENTIFY_EVENT: '$identify',
GROUP_IDENTIFY_EVENT: '$groupidentify',
// localStorageKeys
LAST_EVENT_ID: 'amplitude_lastEventId',
LAST_EVENT_TIME: 'amplitude_lastEventTime',
LAST_IDENTIFY_ID: 'amplitude_lastIdentifyId',
LAST_SEQUENCE_NUMBER: 'amplitude_lastSequenceNumber',
SESSION_ID: 'amplitude_sessionId',
// Used in cookie as well
DEVICE_ID: 'amplitude_deviceId',
OPT_OUT: 'amplitude_optOut',
USER_ID: 'amplitude_userId',
COOKIE_TEST_PREFIX: 'amp_cookie_test',
COOKIE_PREFIX: "amp",
// revenue keys
REVENUE_EVENT: 'revenue_amount',
REVENUE_PRODUCT_ID: '$productId',
REVENUE_QUANTITY: '$quantity',
REVENUE_PRICE: '$price',
REVENUE_REVENUE_TYPE: '$revenueType',
AMP_DEVICE_ID_PARAM: 'amp_device_id',
// url param
REFERRER: 'referrer',
// UTM Params
UTM_SOURCE: 'utm_source',
UTM_MEDIUM: 'utm_medium',
UTM_CAMPAIGN: 'utm_campaign',
UTM_TERM: 'utm_term',
UTM_CONTENT: 'utm_content',
ATTRIBUTION_EVENT: '[Amplitude] Attribution Captured'
};
/* jshint bitwise: false */
/*
* UTF-8 encoder/decoder
* http://www.webtoolkit.info/
*/
var UTF8 = {
encode: function encode(s) {
var utftext = '';
for (var n = 0; n < s.length; n++) {
var c = s.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
} else if (c > 127 && 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 decode(utftext) {
var s = '';
var i = 0;
var c = 0,
c1 = 0,
c2 = 0;
while (i < utftext.length) {
c = utftext.charCodeAt(i);
if (c < 128) {
s += String.fromCharCode(c);
i++;
} else if (c > 191 && 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;
}
};
/* jshint bitwise: false */
/*
* Base64 encoder/decoder
* http://www.webtoolkit.info/
*/
var Base64 = {
_keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
encode: function encode(input) {
try {
if (window.btoa && window.atob) {
return window.btoa(unescape(encodeURIComponent(input)));
}
} catch (e) {//log(e);
}
return Base64._encode(input);
},
_encode: function _encode(input) {
var output = '';
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = UTF8.encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 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 decode(input) {
try {
if (window.btoa && window.atob) {
return decodeURIComponent(escape(window.atob(input)));
}
} catch (e) {//log(e);
}
return Base64._decode(input);
},
_decode: function _decode(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 < input.length) {
enc1 = Base64._keyStr.indexOf(input.charAt(i++));
enc2 = Base64._keyStr.indexOf(input.charAt(i++));
enc3 = Base64._keyStr.indexOf(input.charAt(i++));
enc4 = Base64._keyStr.indexOf(input.charAt(i++));
chr1 = enc1 << 2 | enc2 >> 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;
}
};
/**
* toString ref.
* @private
*/
var toString = Object.prototype.toString;
/**
* Return the type of `val`.
* @private
* @param {Mixed} val
* @return {String}
* @api public
*/
function type (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' && typeof Buffer.isBuffer === 'function' && Buffer.isBuffer(val)) {
return 'buffer';
}
val = val.valueOf ? val.valueOf() : Object.prototype.valueOf.apply(val);
return _typeof(val);
}
var logLevels = {
DISABLE: 0,
ERROR: 1,
WARN: 2,
INFO: 3
};
var logLevel = logLevels.WARN;
var setLogLevel = function setLogLevel(logLevelName) {
if (logLevels.hasOwnProperty(logLevelName)) {
logLevel = logLevels[logLevelName];
}
};
var getLogLevel = function getLogLevel() {
return logLevel;
};
var log = {
error: function error(s) {
if (logLevel >= logLevels.ERROR) {
_log(s);
}
},
warn: function warn(s) {
if (logLevel >= logLevels.WARN) {
_log(s);
}
},
info: function info(s) {
if (logLevel >= logLevels.INFO) {
_log(s);
}
}
};
var _log = function _log(s) {
try {
console.log('[Amplitude] ' + s);
} catch (e) {// console logging not available
}
};
var isEmptyString = function isEmptyString(str) {
return !str || str.length === 0;
};
var sessionStorageEnabled = function sessionStorageEnabled() {
try {
if (window.sessionStorage) {
return true;
}
} catch (e) {} // sessionStorage disabled
return false;
}; // truncate string values in event and user properties so that request size does not get too large
var truncate = function truncate(value) {
if (type(value) === 'array') {
for (var i = 0; i < value.length; i++) {
value[i] = truncate(value[i]);
}
} else if (type(value) === 'object') {
for (var key in value) {
if (value.hasOwnProperty(key)) {
value[key] = truncate(value[key]);
}
}
} else {
value = _truncateValue(value);
}
return value;
};
var _truncateValue = function _truncateValue(value) {
if (type(value) === 'string') {
return value.length > Constants.MAX_STRING_LENGTH ? value.substring(0, Constants.MAX_STRING_LENGTH) : value;
}
return value;
};
var validateInput = function validateInput(input, name, expectedType) {
if (type(input) !== expectedType) {
log.error('Invalid ' + name + ' input type. Expected ' + expectedType + ' but received ' + type(input));
return false;
}
return true;
}; // do some basic sanitization and type checking, also catch property dicts with more than 1000 key/value pairs
var validateProperties = function validateProperties(properties) {
var propsType = type(properties);
if (propsType !== 'object') {
log.error('Error: invalid properties format. Expecting Javascript object, received ' + propsType + ', ignoring');
return {};
}
if (Object.keys(properties).length > Constants.MAX_PROPERTY_KEYS) {
log.error('Error: too many properties (more than 1000), ignoring');
return {};
}
var copy = {}; // create a copy with all of the valid properties
for (var property in properties) {
if (!properties.hasOwnProperty(property)) {
continue;
} // validate key
var key = property;
var keyType = type(key);
if (keyType !== 'string') {
key = String(key);
log.warn('WARNING: Non-string property key, received type ' + keyType + ', coercing to string "' + key + '"');
} // validate value
var value = validatePropertyValue(key, properties[property]);
if (value === null) {
continue;
}
copy[key] = value;
}
return copy;
};
var invalidValueTypes = ['nan', 'function', 'arguments', 'regexp', 'element'];
var validatePropertyValue = function validatePropertyValue(key, value) {
var valueType = type(value);
if (invalidValueTypes.indexOf(valueType) !== -1) {
log.warn('WARNING: Property key "' + key + '" with invalid value type ' + valueType + ', ignoring');
value = null;
} else if (valueType === 'undefined') {
value = null;
} else if (valueType === 'error') {
value = String(value);
log.warn('WARNING: Property key "' + key + '" with value type error, coercing to ' + value);
} else if (valueType === 'array') {
// check for nested arrays or objects
var arrayCopy = [];
for (var i = 0; i < value.length; i++) {
var element = value[i];
var elemType = type(element);
if (elemType === 'array') {
log.warn('WARNING: Cannot have ' + elemType + ' nested in an array property value, skipping');
continue;
} else if (elemType === 'object') {
arrayCopy.push(validateProperties(element));
} else {
arrayCopy.push(validatePropertyValue(key, element));
}
}
value = arrayCopy;
} else if (valueType === 'object') {
value = validateProperties(value);
}
return value;
};
var validateGroups = function validateGroups(groups) {
var groupsType = type(groups);
if (groupsType !== 'object') {
log.error('Error: invalid groups format. Expecting Javascript object, received ' + groupsType + ', ignoring');
return {};
}
var copy = {}; // create a copy with all of the valid properties
for (var group in groups) {
if (!groups.hasOwnProperty(group)) {
continue;
} // validate key
var key = group;
var keyType = type(key);
if (keyType !== 'string') {
key = String(key);
log.warn('WARNING: Non-string groupType, received type ' + keyType + ', coercing to string "' + key + '"');
} // validate value
var value = validateGroupName(key, groups[group]);
if (value === null) {
continue;
}
copy[key] = value;
}
return copy;
};
var validateGroupName = function validateGroupName(key, groupName) {
var groupNameType = type(groupName);
if (groupNameType === 'string') {
return groupName;
}
if (groupNameType === 'date' || groupNameType === 'number' || groupNameType === 'boolean') {
groupName = String(groupName);
log.warn('WARNING: Non-string groupName, received type ' + groupNameType + ', coercing to string "' + groupName + '"');
return groupName;
}
if (groupNameType === 'array') {
// check for nested arrays or objects
var arrayCopy = [];
for (var i = 0; i < groupName.length; i++) {
var element = groupName[i];
var elemType = type(element);
if (elemType === 'array' || elemType === 'object') {
log.warn('WARNING: Skipping nested ' + elemType + ' in array groupName');
continue;
} else if (elemType === 'string') {
arrayCopy.push(element);
} else if (elemType === 'date' || elemType === 'number' || elemType === 'boolean') {
element = String(element);
log.warn('WARNING: Non-string groupName, received type ' + elemType + ', coercing to string "' + element + '"');
arrayCopy.push(element);
}
}
return arrayCopy;
}
log.warn('WARNING: Non-string groupName, received type ' + groupNameType + '. Please use strings or array of strings for groupName');
}; // parses the value of a url param (for example ?gclid=1234&...)
var getQueryParam = function getQueryParam(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, " "));
};
var utils = {
setLogLevel: setLogLevel,
getLogLevel: getLogLevel,
logLevels: logLevels,
log: log,
isEmptyString: isEmptyString,
getQueryParam: getQueryParam,
sessionStorageEnabled: sessionStorageEnabled,
truncate: truncate,
validateGroups: validateGroups,
validateInput: validateInput,
validateProperties: validateProperties
};
var getLocation = function getLocation() {
return window.location;
};
// A URL safe variation on the the list of Base64 characters
var base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
var base64Id = function base64Id() {
var str = '';
for (var i = 0; i < 22; ++i) {
str += base64Chars.charAt(Math.floor(Math.random() * 64));
}
return str;
};
var get = function get(name) {
try {
var ca = document.cookie.split(';');
var value = null;
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1, c.length);
}
if (c.indexOf(name) === 0) {
value = c.substring(name.length, c.length);
break;
}
}
return value;
} catch (e) {
return null;
}
};
var set = function set(name, value, opts) {
var expires = value !== null ? opts.expirationDays : -1;
if (expires) {
var date = new Date();
date.setTime(date.getTime() + expires * 24 * 60 * 60 * 1000);
expires = date;
}
var str = name + '=' + value;
if (expires) {
str += '; expires=' + expires.toUTCString();
}
str += '; path=/';
if (opts.domain) {
str += '; domain=' + opts.domain;
}
if (opts.secure) {
str += '; Secure';
}
if (opts.sameSite) {
str += '; SameSite=' + opts.sameSite;
}
document.cookie = str;
}; // test that cookies are enabled - navigator.cookiesEnabled yields false positives in IE, need to test directly
var areCookiesEnabled = function areCookiesEnabled() {
var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var uid = String(new Date());
try {
var cookieName = Constants.COOKIE_TEST_PREFIX + base64Id();
set(cookieName, uid, opts);
var _areCookiesEnabled = get(cookieName + '=') === uid;
set(cookieName, null, opts);
return _areCookiesEnabled;
} catch (e) {}
return false;
};
var baseCookie = {
set: set,
get: get,
areCookiesEnabled: areCookiesEnabled
};
var getHost = function getHost(url) {
var a = document.createElement('a');
a.href = url;
return a.hostname || location.hostname;
};
var topDomain = function topDomain(url) {
var host = getHost(url);
var parts = host.split('.');
var levels = [];
var cname = '_tldtest_' + base64Id();
for (var i = parts.length - 2; i >= 0; --i) {
levels.push(parts.slice(i).join('.'));
}
for (var _i = 0; _i < levels.length; ++_i) {
var domain = levels[_i];
var opts = {
domain: '.' + domain
};
baseCookie.set(cname, 1, opts);
if (baseCookie.get(cname)) {
baseCookie.set(cname, null, opts);
return domain;
}
}
return '';
};
/*
* Cookie data
*/
var _options = {
expirationDays: undefined,
domain: undefined
};
var reset = function reset() {
_options = {
expirationDays: undefined,
domain: undefined
};
};
var options = function options(opts) {
if (arguments.length === 0) {
return _options;
}
opts = opts || {};
_options.expirationDays = opts.expirationDays;
_options.secure = opts.secure;
_options.sameSite = opts.sameSite;
var domain = !utils.isEmptyString(opts.domain) ? opts.domain : '.' + topDomain(getLocation().href);
var token = Math.random();
_options.domain = domain;
set$1('amplitude_test', token);
var stored = get$1('amplitude_test');
if (!stored || stored !== token) {
domain = null;
}
remove('amplitude_test');
_options.domain = domain;
return _options;
};
var _domainSpecific = function _domainSpecific(name) {
// differentiate between cookies on different domains
var suffix = '';
if (_options.domain) {
suffix = _options.domain.charAt(0) === '.' ? _options.domain.substring(1) : _options.domain;
}
return name + suffix;
};
var get$1 = function get(name) {
var nameEq = _domainSpecific(name) + '=';
var value = baseCookie.get(nameEq);
try {
if (value) {
return JSON.parse(Base64.decode(value));
}
} catch (e) {
return null;
}
return null;
};
var set$1 = function set(name, value) {
try {
baseCookie.set(_domainSpecific(name), Base64.encode(JSON.stringify(value)), _options);
return true;
} catch (e) {
return false;
}
};
var setRaw = function setRaw(name, value) {
try {
baseCookie.set(_domainSpecific(name), value, _options);
return true;
} catch (e) {
return false;
}
};
var getRaw = function getRaw(name) {
var nameEq = _domainSpecific(name) + '=';
return baseCookie.get(nameEq);
};
var remove = function remove(name) {
try {
baseCookie.set(_domainSpecific(name), null, _options);
return true;
} catch (e) {
return false;
}
};
var Cookie = {
reset: reset,
options: options,
get: get$1,
set: set$1,
remove: remove,
setRaw: setRaw,
getRaw: getRaw
};
/* jshint -W020, unused: false, noempty: false, boss: true */
/*
* Implement localStorage to support Firefox 2-3 and IE 5-7
*/
var localStorage; // jshint ignore:line
{
// test that Window.localStorage is available and works
var windowLocalStorageAvailable = function windowLocalStorageAvailable() {
var uid = new Date();
var result;
try {
window.localStorage.setItem(uid, uid);
result = window.localStorage.getItem(uid) === String(uid);
window.localStorage.removeItem(uid);
return result;
} catch (e) {// localStorage not available
}
return false;
};
if (windowLocalStorageAvailable()) {
localStorage = window.localStorage;
} else if (window.globalStorage) {
// Firefox 2-3 use globalStorage
// See https://developer.mozilla.org/en/dom/storage#globalStorage
try {
localStorage = window.globalStorage[window.location.hostname];
} catch (e) {// Something bad happened...
}
} else if (typeof document !== 'undefined') {
// IE 5-7 use userData
// See http://msdn.microsoft.com/en-us/library/ms531424(v=vs.85).aspx
var div = document.createElement('div'),
attrKey = 'localStorage';
div.style.display = 'none';
document.getElementsByTagName('head')[0].appendChild(div);
if (div.addBehavior) {
div.addBehavior('#default#userdata');
localStorage = {
length: 0,
setItem: function setItem(k, v) {
div.load(attrKey);
if (!div.getAttribute(k)) {
this.length++;
}
div.setAttribute(k, v);
div.save(attrKey);
},
getItem: function getItem(k) {
div.load(attrKey);
return div.getAttribute(k);
},
removeItem: function removeItem(k) {
div.load(attrKey);
if (div.getAttribute(k)) {
this.length--;
}
div.removeAttribute(k);
div.save(attrKey);
},
clear: function clear() {
div.load(attrKey);
var i = 0;
var attr;
while (attr = div.XMLDocument.documentElement.attributes[i++]) {
div.removeAttribute(attr.name);
}
div.save(attrKey);
this.length = 0;
},
key: function key(k) {
div.load(attrKey);
return div.XMLDocument.documentElement.attributes[k];
}
};
div.load(attrKey);
localStorage.length = div.XMLDocument.documentElement.attributes.length;
}
}
if (!localStorage) {
localStorage = {
length: 0,
setItem: function setItem(k, v) {},
getItem: function getItem(k) {},
removeItem: function removeItem(k) {},
clear: function clear() {},
key: function key(k) {}
};
}
}
var localStorage$1 = localStorage;
/* jshint -W020, unused: false, noempty: false, boss: true */
var cookieStorage = function cookieStorage() {
this.storage = null;
};
cookieStorage.prototype.getStorage = function () {
if (this.storage !== null) {
return this.storage;
}
if (baseCookie.areCookiesEnabled()) {
this.storage = Cookie;
} else {
// if cookies disabled, fallback to localstorage
// note: localstorage does not persist across subdomains
var keyPrefix = 'amp_cookiestore_';
this.storage = {
_options: {
expirationDays: undefined,
domain: undefined,
secure: false
},
reset: function reset() {
this._options = {
expirationDays: undefined,
domain: undefined,
secure: false
};
},
options: function options(opts) {
if (arguments.length === 0) {
return this._options;
}
opts = opts || {};
this._options.expirationDays = opts.expirationDays || this._options.expirationDays; // localStorage is specific to subdomains
this._options.domain = opts.domain || this._options.domain || window && window.location && window.location.hostname;
return this._options.secure = opts.secure || false;
},
get: function get(name) {
try {
return JSON.parse(localStorage$1.getItem(keyPrefix + name));
} catch (e) {}
return null;
},
set: function set(name, value) {
try {
localStorage$1.setItem(keyPrefix + name, JSON.stringify(value));
return true;
} catch (e) {}
return false;
},
remove: function remove(name) {
try {
localStorage$1.removeItem(keyPrefix + name);
} catch (e) {
return false;
}
}
};
}
return this.storage;
};
/**
* MetadataStorage involves SDK data persistance
* storage priority: cookies -> localStorage -> in memory
* if in localStorage, unable track users between subdomains
* if in memory, then memory can't be shared between different tabs
*/
var MetadataStorage =
/*#__PURE__*/
function () {
function MetadataStorage(_ref) {
var storageKey = _ref.storageKey,
disableCookies = _ref.disableCookies,
domain = _ref.domain,
secure = _ref.secure,
sameSite = _ref.sameSite,
expirationDays = _ref.expirationDays;
_classCallCheck(this, MetadataStorage);
this.storageKey = storageKey;
this.domain = domain;
this.secure = secure;
this.sameSite = sameSite;
this.expirationDays = expirationDays;
this.cookieDomain = '';
{
var writableTopDomain = topDomain(getLocation().href);
this.cookieDomain = domain || (writableTopDomain ? '.' + writableTopDomain : null);
}
this.disableCookieStorage = disableCookies || !baseCookie.areCookiesEnabled({
domain: this.cookieDomain,
secure: this.secure,
sameSite: this.sameSite,
expirationDays: this.expirationDays
});
}
_createClass(MetadataStorage, [{
key: "getCookieStorageKey",
value: function getCookieStorageKey() {
if (!this.domain) {
return this.storageKey;
}
var suffix = this.domain.charAt(0) === '.' ? this.domain.substring(1) : this.domain;
return "".concat(this.storageKey).concat(suffix ? "_".concat(suffix) : '');
}
/*
* Data is saved as delimited values rather than JSO to minimize cookie space
* Should not change order of the items
*/
}, {
key: "save",
value: function save(_ref2) {
var deviceId = _ref2.deviceId,
userId = _ref2.userId,
optOut = _ref2.optOut,
sessionId = _ref2.sessionId,
lastEventTime = _ref2.lastEventTime,
eventId = _ref2.eventId,
identifyId = _ref2.identifyId,
sequenceNumber = _ref2.sequenceNumber;
var value = [deviceId, Base64.encode(userId || ''), // used to convert not unicode to alphanumeric since cookies only use alphanumeric
optOut ? '1' : '', sessionId ? sessionId.toString(32) : '0', // generated when instantiated, timestamp (but re-uses session id in cookie if not expired) @TODO clients may want custom session id
lastEventTime ? lastEventTime.toString(32) : '0', // last time an event was set
eventId ? eventId.toString(32) : '0', identifyId ? identifyId.toString(32) : '0', sequenceNumber ? sequenceNumber.toString(32) : '0'].join('.');
if (this.disableCookieStorage) {
localStorage$1.setItem(this.storageKey, value);
} else {
baseCookie.set(this.getCookieStorageKey(), value, {
domain: this.cookieDomain,
secure: this.secure,
sameSite: this.sameSite,
expirationDays: this.expirationDays
});
}
}
}, {
key: "load",
value: function load() {
var str;
if (!this.disableCookieStorage) {
str = baseCookie.get(this.getCookieStorageKey() + '=');
}
if (!str) {
str = localStorage$1.getItem(this.storageKey);
}
if (!str) {
return null;
}
var values = str.split('.');
var userId = null;
if (values[1]) {
try {
userId = Base64.decode(values[1]);
} catch (e) {
userId = null;
}
}
return {
deviceId: values[0],
userId: userId,
optOut: values[2] === '1',
sessionId: parseInt(values[3], 32),
lastEventTime: parseInt(values[4], 32),
eventId: parseInt(values[5], 32),
identifyId: parseInt(values[6], 32),
sequenceNumber: parseInt(values[7], 32)
};
}
}]);
return MetadataStorage;
}();
var getUtmData = function getUtmData(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 fetchParam(queryName, query, cookieName, cookie) {
return utils.getQueryParam(queryName, query) || utils.getQueryParam(cookieName, cookie);
};
var utmSource = fetchParam(Constants.UTM_SOURCE, query, 'utmcsr', cookie);
var utmMedium = fetchParam(Constants.UTM_MEDIUM, query, 'utmcmd', cookie);
var utmCampaign = fetchParam(Constants.UTM_CAMPAIGN, query, 'utmccn', cookie);
var utmTerm = fetchParam(Constants.UTM_TERM, query, 'utmctr', cookie);
var utmContent = fetchParam(Constants.UTM_CONTENT, query, 'utmcct', cookie);
var utmData = {};
var addIfNotNull = function addIfNotNull(key, value) {
if (!utils.isEmptyString(value)) {
utmData[key] = value;
}
};
addIfNotNull(Constants.UTM_SOURCE, utmSource);
addIfNotNull(Constants.UTM_MEDIUM, utmMedium);
addIfNotNull(Constants.UTM_CAMPAIGN, utmCampaign);
addIfNotNull(Constants.UTM_TERM, utmTerm);
addIfNotNull(Constants.UTM_CONTENT, utmContent);
return utmData;
};
/*
* Wrapper for a user properties JSON object that supports operations.
* Note: if a user property is used in multiple operations on the same Identify object,
* only the first operation will be saved, and the rest will be ignored.
*/
var AMP_OP_ADD = '$add';
var AMP_OP_APPEND = '$append';
var AMP_OP_CLEAR_ALL = '$clearAll';
var AMP_OP_PREPEND = '$prepend';
var AMP_OP_SET = '$set';
var AMP_OP_SET_ONCE = '$setOnce';
var AMP_OP_UNSET = '$unset';
/**
* Identify API - instance constructor. Identify objects are a wrapper for user property operations.
* Each method adds a user property operation to the Identify object, and returns the same Identify object,
* allowing you to chain multiple method calls together.
* Note: if the same user property is used in multiple operations on a single Identify object,
* only the first operation on that property will be saved, and the rest will be ignored.
* @constructor Identify
* @public
* @example var identify = new amplitude.Identify();
*/
var Identify = function Identify() {
this.userPropertiesOperations = {};
this.properties = []; // keep track of keys that have been added
};
/**
* Increment a user property by a given value (can also be negative to decrement).
* If the user property does not have a value set yet, it will be initialized to 0 before being incremented.
* @public
* @param {string} property - The user property key.
* @param {number|string} value - The amount by which to increment the user property. Allows numbers as strings (ex: '123').
* @return {Identify} Returns the same Identify object, allowing you to chain multiple method calls together.
* @example var identify = new amplitude.Identify().add('karma', 1).add('friends', 1);
* amplitude.identify(identify); // send the Identify call
*/
Identify.prototype.add = function (property, value) {
if (type(value) === 'number' || type(value) === 'string') {
this._addOperation(AMP_OP_ADD, property, value);
} else {
utils.log.error('Unsupported type for value: ' + type(value) + ', expecting number or string');
}
return this;
};
/**
* Append a value or values to a user property.
* If the user property does not have a value set yet,
* it will be initialized to an empty list before the new values are appended.
* If the user property has an existing value and it is not a list,
* the existing value will be converted into a list with the new values appended.
* @public
* @param {string} property - The user property key.
* @param {number|string|list|object} value - A value or values to append.
* Values can be numbers, strings, lists, or object (key:value dict will be flattened).
* @return {Identify} Returns the same Identify object, allowing you to chain multiple method calls together.
* @example var identify = new amplitude.Identify().append('ab-tests', 'new-user-tests');
* identify.append('some_list', [1, 2, 3, 4, 'values']);
* amplitude.identify(identify); // send the Identify call
*/
Identify.prototype.append = function (property, value) {
this._addOperation(AMP_OP_APPEND, property, value);
return this;
};
/**
* Clear all user properties for the current user.
* SDK user should instead call amplitude.clearUserProperties() instead of using this.
* $clearAll needs to be sent on its own Identify object. If there are already other operations, then don't add $clearAll.
* If $clearAll already in an Identify object, don't allow other operations to be added.
* @private
*/
Identify.prototype.clearAll = function () {
if (Object.keys(this.userPropertiesOperations).length > 0) {
if (!this.userPropertiesOperations.hasOwnProperty(AMP_OP_CLEAR_ALL)) {
utils.log.error('Need to send $clearAll on its own Identify object without any other operations, skipping $clearAll');
}
return this;
}
this.userPropertiesOperations[AMP_OP_CLEAR_ALL] = '-';
return this;
};
/**
* Prepend a value or values to a user property.
* Prepend means inserting the value or values at the front of a list.
* If the user property does not have a value set yet,
* it will be initialized to an empty list before the new values are prepended.
* If the user property has an existing value and it is not a list,
* the existing value will be converted into a list with the new values prepended.
* @public
* @param {string} property - The user property key.
* @param {number|string|list|object} value - A value or values to prepend.
* Values can be numbers, strings, lists, or object (key:value dict will be flattened).
* @return {Identify} Returns the same Identify object, allowing you to chain multiple method calls together.
* @example var identify = new amplitude.Identify().prepend('ab-tests', 'new-user-tests');
* identify.prepend('some_list', [1, 2, 3, 4, 'values']);
* amplitude.identify(identify); // send the Identify call
*/
Identify.prototype.prepend = function (property, value) {
this._addOperation(AMP_OP_PREPEND, property, value);
return this;
};
/**
* Sets the value of a given user property. If a value already exists, it will be overwriten with the new value.
* @public
* @param {string} property - The user property key.
* @param {number|string|list|boolean|object} value - A value or values to set.
* Values can be numbers, strings, lists, or object (key:value dict will be flattened).
* @return {Identify} Returns the same Identify object, allowing you to chain multiple method calls together.
* @example var identify = new amplitude.Identify().set('user_type', 'beta');
* identify.set('name', {'first': 'John', 'last': 'Doe'}); // dict is flattened and becomes name.first: John, name.last: Doe
* amplitude.identify(identify); // send the Identify call
*/
Identify.prototype.set = function (property, value) {
this._addOperation(AMP_OP_SET, property, value);
return this;
};
/**
* Sets the value of a given user property only once. Subsequent setOnce operations on that user property will be ignored;
* however, that user property can still be modified through any of the other operations.
* Useful for capturing properties such as 'initial_signup_date', 'initial_referrer', etc.
* @public
* @param {string} property - The user property key.
* @param {number|string|list|boolean|object} value - A value or values to set once.
* Values can be numbers, strings, lists, or object (key:value dict will be flattened).
* @return {Identify} Returns the same Identify object, allowing you to chain multiple method calls together.
* @example var identify = new amplitude.Identify().setOnce('sign_up_date', '2016-04-01');
* amplitude.identify(identify); // send the Identify call
*/
Identify.prototype.setOnce = function (property, value) {
this._addOperation(AMP_OP_SET_ONCE, property, value);
return this;
};
/**
* Unset and remove a user property. This user property will no longer show up in a user's profile.
* @public
* @param {string} property - The user property key.
* @return {Identify} Returns the same Identify object, allowing you to chain multiple method calls together.
* @example var identify = new amplitude.Identify().unset('user_type').unset('age');
* amplitude.identify(identify); // send the Identify call
*/
Identify.prototype.unset = function (property) {
this._addOperation(AMP_OP_UNSET, property, '-');
return this;
};
/**
* Helper function that adds operation to the Identify's object
* Handle's filtering of duplicate user property keys, and filtering for clearAll.
* @private
*/
Identify.prototype._addOperation = function (operation, property, value) {
// check that the identify doesn't already contain a clearAll
if (this.userPropertiesOperations.hasOwnProperty(AMP_OP_CLEAR_ALL)) {
utils.log.error('This identify already contains a $clearAll operation, skipping operation ' + operation);
return;
} // check that property wasn't already used in this Identify
if (this.properties.indexOf(property) !== -1) {
utils.log.error('User property "' + property + '" already used in this identify, skipping operation ' + operation);
return;
}
if (!this.userPropertiesOperations.hasOwnProperty(operation)) {
this.userPropertiesOperations[operation] = {};
}
this.userPropertiesOperations[operation][property] = value;
this.properties.push(property);
};
/*
* Simple AJAX request object
*/
var Request = function Request(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 () {
// status code not available from xdr, try string matching on responseText
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));
} //log('sent request to ' + this.url + ' with data ' + decodeURIComponent(queryString(this.data)));
};
/**
* Revenue API - instance constructor. Wrapper for logging Revenue data. Revenue objects get passed to amplitude.logRevenueV2 to send to Amplitude servers.
* Each method updates a revenue property in the Revenue object, and returns the same Revenue object,
* allowing you to chain multiple method calls together.
*
* Note: price is a required field to log revenue events.
* If quantity is not specified then defaults to 1.
* @constructor Revenue
* @public
* @example var revenue = new amplitude.Revenue();
*/
var Revenue = function Revenue() {
// required fields
this._price = null; // optional fields
this._productId = null;
this._quantity = 1;
this._revenueType = null;
this._properties = null;
};
/**
* Set a value for the product identifer.
* @public
* @param {string} productId - The value for the product identifier. Empty and invalid strings are ignored.
* @return {Revenue} Returns the same Revenue object, allowing you to chain multiple method calls together.
* @example var revenue = new amplitude.Revenue().setProductId('productIdentifier').setPrice(10.99);
* amplitude.logRevenueV2(revenue);
*/
Revenue.prototype.setProductId = function setProductId(productId) {
if (type(productId) !== 'string') {
utils.log.error('Unsupported type for productId: ' + type(productId) + ', expecting string');
} else if (utils.isEmptyString(productId)) {
utils.log.error('Invalid empty productId');
} else {
this._productId = productId;
}
return this;
};
/**
* Set a value for the quantity. Note revenue amount is calculated as price * quantity.
* @public
* @param {number} quantity - Integer value for the quantity. If not set, quantity defaults to 1.
* @return {Revenue} Returns the same Revenue object, allowing you to chain multiple method calls together.
* @example var revenue = new amplitude.Revenue().setProductId('productIdentifier').setPrice(10.99).setQuantity(5);
* amplitude.logRevenueV2(revenue);
*/
Revenue.prototype.setQuantity = function setQuantity(quantity) {
if (type(quantity) !== 'number') {
utils.log.error('Unsupported type for quantity: ' + type(quantity) + ', expecting number');
} else {
this._quantity = parseInt(quantity);
}
return this;
};
/**
* Set a value for the price. This field is required for all revenue being logged.
*
* Note: revenue amount is calculated as price * quantity.
* @public
* @param {number} price - Double value for the quantity.
* @return {Revenue} Returns the same Revenue object, allowing you to chain multiple method calls together.
* @example var revenue = new amplitude.Revenue().setProductId('productIdentifier').setPrice(10.99);
* amplitude.logRevenueV2(revenue);
*/
Revenue.prototype.setPrice = function setPrice(price) {
if (type(price) !== 'number') {
utils.log.error('Unsupported type for price: ' + type(price) + ', expecting number');
} else {
this._price = price;
}
return this;
};
/**
* Set a value for the revenueType (for example purchase, cost, tax, refund, etc).
* @public
* @param {string} revenueType - RevenueType to designate.
* @return {Revenue} Returns the same Revenue object, allowing you to chain multiple method calls together.
* @example var revenue = new amplitude.Revenue().setProductId('productIdentifier').setPrice(10.99).setRevenueType('purchase');
* amplitude.logRevenueV2(revenue);
*/
Revenue.prototype.setRevenueType = function setRevenueType(revenueType) {
if (type(revenueType) !== 'string') {
utils.log.error('Unsupported type for revenueType: ' + type(revenueType) + ', expecting string');
} else {
this._revenueType = revenueType;
}
return this;
};
/**
* Set event properties for the revenue event.
* @public
* @param {object} eventProperties - Revenue event properties to set.
* @return {Revenue} Returns the same Revenue object, allowing you to chain multiple method calls together.
* @example var event_properties = {'city': 'San Francisco'};
* var revenue = new amplitude.Revenue().setProductId('productIdentifier').setPrice(10.99).setEventProperties(event_properties);
* amplitude.logRevenueV2(revenue);
*/
Revenue.prototype.setEventProperties = function setEventProperties(eventProperties) {
if (type(eventProperties) !== 'object') {
utils.log.error('Unsupported type for eventProperties: ' + type(eventProperties) + ', expecting object');
} else {
this._properties = utils.validateProperties(eventProperties);
}
return this;
};
/**
* @private
*/
Revenue.prototype._isValidRevenue = function _isValidRevenue() {
if (type(this._price) !== 'number') {
utils.log.error('Invalid revenue, need to set price field');
return false;
}
return true;
};
/**
* @private
*/
Revenue.prototype._toJSONObject = function _toJSONObject() {
var obj = type(this._properties) === 'object' ? this._properties : {};
if (this._productId !== null) {
obj[Constants.REVENUE_PRODUCT_ID] = this._productId;
}
if (this._quantity !== null) {
obj[Constants.REVENUE_QUANTITY] = this._quantity;
}
if (this._price !== null) {
obj[Constants.REVENUE_PRICE] = this._price;
}
if (this._revenueType !== null) {
obj[Constants.REVENUE_REVENUE_TYPE] = this._revenueType;
}
return obj;
};
/* jshint bitwise: false, laxbreak: true */
/**
* Source: [jed's gist]{@link https://gist.github.com/982883}.
* Returns a random v4 UUID of the form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,
* where each x is replaced with a random hexadecimal digit from 0 to f, and
* y is replaced with a random hexadecimal digit from 8 to b.
* Used to generate UUIDs for deviceIds.
* @private
*/
var uuid = function uuid(a) {
return a // if the placeholder was passed, return
? ( // a random number from 0 to 15
a ^ // unless b is 8,
Math.random() // in which case
* 16 // a random number from
>> a / 4 // 8 to 11
).toString(16) // in hexadecimal
: ( // or otherwise a concatenated string:
[1e7] + // 10000000 +
-1e3 + // -1000 +
-4e3 + // -4000 +
-8e3 + // -80000000 +
-1e11 // -100000000000,
).replace( // replacing
/[018]/g, // zeroes, ones, and eights with
uuid // random hex digits
);
};
var version = "7.2.2";
var getLanguage = function getLanguage() {
return navigator && (navigator.languages && navigator.languages[0] || navigator.language || navigator.userLanguage) || '';
};
var language = {
getLanguage: getLanguage
};
var platform = 'Web';
var DEFAULT_OPTIONS = {
apiEndpoint: 'api.amplitude.com',
batchEvents: false,
cookieExpiration: 365 * 10,
cookieName: 'amplitude_id',
// this is a deprecated option
sameSiteCookie: 'Lax',
// cookie privacy policy
cookieForceUpgrade: false,
deferInitialization: false,
disableCookies: false,
deviceIdFromUrlParam: false,
domain: '',
eventUploadPeriodMillis: 30 * 1000,
// 30s
eventUploadThreshold: 30,
forceHttps: true,
includeGclid: false,
includeReferrer: false,
includeUtm: false,
language: language.getLanguage(),
logLevel: 'WARN',
logAttributionCapturedEvent: false,
optOut: false,
onError: function onError() {},
platform: platform,
savedMaxCount: 1000,
saveEvents: true,
saveParamsReferrerOncePerSession: true,
secureCookie: false,
sessionTimeout: 30 * 60 * 1000,
trackingOptions: {
city: true,
country: true,
carrier: true,
device_manufacturer: true,
device_model: true,
dma: true,
ip_address: true,
language: true,
os_name: true,
os_version: true,
platform: true,
region: true,
version_name: true
},
unsetParamsReferrerOnNewSession: false,
unsentKey: 'amplitude_unsent',
unsentIdentifyKey: 'amplitude_unsent_identify',
uploadBatchSize: 100
};
var AsyncStorage;
var DeviceInfo;
/**
* AmplitudeClient SDK API - instance constructor.
* The Amplitude class handles creation of client instances, all you need to do is call amplitude.getInstance()
* @constructor AmplitudeClient
* @public
* @example var amplitudeClient = new AmplitudeClient();
*/
var AmplitudeClient = function AmplitudeClient(instanceName) {
this._instanceName = utils.isEmptyString(instanceName) ? Constants.DEFAULT_INSTANCE : instanceName.toLowerCase();
this._unsentEvents = [];
this._unsentIdentifys = [];
this._ua = new UAParser(navigator.userAgent).getResult();
this.options = _objectSpread({}, DEFAULT_OPTIONS, {
trackingOptions: _objectSpread({}, DEFAULT_OPTIONS.trackingOptions)
});
this.cookieStorage = new cookieStorage().getStorage();
this._q = []; // queue for proxied functions before script load
this._sending = false;
this._updateScheduled = false;
this._onInit = []; // event meta data
this._eventId = 0;
this._identifyId = 0;
this._lastEventTime = null;
this._newSession = false; // sequence used for by frontend for prioritizing event send retries
this._sequenceNumber = 0;
this._sessionId = null;
this._isInitialized = false;
this._userAgent = navigator && navigator.userAgent || null;
};
AmplitudeClient.prototype.Identify = Identify;
AmplitudeClient.prototype.Revenue = Revenue;
/**
* Initializes the Amplitude Javascript SDK with your apiKey and any optional configurations.
* This is required before any other methods can be called.
* @public
* @param {string} apiKey - The API key for your app.
* @param {string} opt_userId - (optional) An identifier for this user.
* @param {object} opt_config - (optional) Configuration options.
* See [options.js](https://github.com/amplitude/Amplitude-JavaScript/blob/master/src/options.js#L14) for list of options and default values.
* @param {function} opt_callback - (optional) Provide a callback function to run after initialization is complete.
* @example amplitudeClient.init('API_KEY', 'USER_ID', {includeReferrer: true, includeUtm: true}, function() { alert('init complete'); });
*/
AmplitudeClient.prototype.init = function init(apiKey, opt_userId, opt_config, opt_callback) {
var _this = this;
if (type(apiKey) !== 'string' || utils.isEmptyString(apiKey)) {
utils.log.error('Invalid apiKey. Please re-initialize with a valid apiKey');
return;
}
try {
_parseConfig(this.options, opt_config);
if (this.options.cookieName !== DEFAULT_OPTIONS.cookieName) {
utils.log.warn('The cookieName option is deprecated. We will be ignoring it for newer cookies');
}
this.options.apiKey = apiKey;
this._storageSuffix = '_' + apiKey + (this._instanceName === Constants.DEFAULT_INSTANCE ? '' : '_' + this._instanceName);
this._storageSuffixV5 = apiKey.slice(0, 6);
this._oldCookiename = this.options.cookieName + this._storageSuffix;
this._unsentKey = this.options.unsentKey + this._storageSuffix;
this._unsentIdentifyKey = this.options.unsentIdentifyKey + this._storageSuffix;
this._cookieName = Constants.COOKIE_PREFIX + '_' + this._storageSuffixV5;
this.cookieStorage.options({
expirationDays: this.options.cookieExpiration,
domain: this.options.domain,
secure: this.options.secureCookie,
sameSite: this.options.sameSiteCookie
});
this._metadataStorage = new MetadataStorage({
storageKey: this._cookieName,
disableCookies: this.options.disableCookies,
expirationDays: this.options.cookieExpiration,
domain: this.options.domain,
secure: this.options.secureCookie,
sameSite: this.options.sameSiteCookie
});
var hasOldCookie = !!this.cookieStorage.get(this._oldCookiename);
var hasNewCookie = !!this._metadataStorage.load();
this._useOldCookie = !hasNewCookie && hasOldCookie && !this.options.cookieForceUpgrade;
var hasCookie = hasNewCookie || hasOldCookie;
this.options.domain = this.cookieStorage.options().domain;
if (this.options.deferInitialization && !hasCookie) {
this._deferInitialization(apiKey, opt_userId, opt_config, opt_callback);
return;
}
if (type(this.options.logLevel) === 'string') {
utils.setLogLevel(this.options.logLevel);
}
var trackingOptions = _generateApiPropertiesTrackingConfig(this);
this._apiPropertiesTrackingOptions = Object.keys(trackingOptions).length > 0 ? {
tracking_options: trackingOptions
} : {};
if (this.options.cookieForceUpgrade && hasOldCookie) {
if (!hasNewCookie) {
_upgradeCookieData(this);
}
this.cookieStorage.remove