Skip to content

Instantly share code, notes, and snippets.

@christianromney
Created October 19, 2018 15:23
Show Gist options
  • Save christianromney/53231938229380f645bc208a501bed4b to your computer and use it in GitHub Desktop.
Save christianromney/53231938229380f645bc208a501bed4b to your computer and use it in GitHub Desktop.
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.mixpanel = factory());
}(this, function () { 'use strict';
var Config = {
DEBUG: false,
LIB_VERSION: '2.22.4'
};
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
var window$1;
if (typeof(window) === 'undefined') {
var loc = {
hostname: ''
};
window$1 = {
navigator: { userAgent: '' },
document: {
location: loc,
referrer: ''
},
screen: { width: 0, height: 0 },
location: loc
};
} else {
window$1 = window;
}
/*
* Saved references to long variable names, so that closure compiler can
* minimize file size.
*/
var ArrayProto = Array.prototype;
var FuncProto = Function.prototype;
var ObjProto = Object.prototype;
var slice = ArrayProto.slice;
var toString = ObjProto.toString;
var hasOwnProperty = ObjProto.hasOwnProperty;
var windowConsole = window$1.console;
var navigator$1 = window$1.navigator;
var document$1 = window$1.document;
var windowOpera = window$1.opera;
var screen = window$1.screen;
var userAgent = navigator$1.userAgent;
var nativeBind = FuncProto.bind;
var nativeForEach = ArrayProto.forEach;
var nativeIndexOf = ArrayProto.indexOf;
var nativeIsArray = Array.isArray;
var breaker = {};
var _ = {
trim: function(str) {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill
return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}
};
// Console override
var console$1 = {
/** @type {function(...[*])} */
log: function() {
if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
try {
windowConsole.log.apply(windowConsole, arguments);
} catch (err) {
_.each(arguments, function(arg) {
windowConsole.log(arg);
});
}
}
},
/** @type {function(...[*])} */
error: function() {
if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
var args = ['Mixpanel error:'].concat(_.toArray(arguments));
try {
windowConsole.error.apply(windowConsole, args);
} catch (err) {
_.each(args, function(arg) {
windowConsole.error(arg);
});
}
}
},
/** @type {function(...[*])} */
critical: function() {
if (!_.isUndefined(windowConsole) && windowConsole) {
var args = ['Mixpanel error:'].concat(_.toArray(arguments));
try {
windowConsole.error.apply(windowConsole, args);
} catch (err) {
_.each(args, function(arg) {
windowConsole.error(arg);
});
}
}
}
};
// UNDERSCORE
// Embed part of the Underscore Library
_.bind = function(func, context) {
var args, bound;
if (nativeBind && func.bind === nativeBind) {
return nativeBind.apply(func, slice.call(arguments, 1));
}
if (!_.isFunction(func)) {
throw new TypeError();
}
args = slice.call(arguments, 2);
bound = function() {
if (!(this instanceof bound)) {
return func.apply(context, args.concat(slice.call(arguments)));
}
var ctor = {};
ctor.prototype = func.prototype;
var self = new ctor();
ctor.prototype = null;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) {
return result;
}
return self;
};
return bound;
};
_.bind_instance_methods = function(obj) {
for (var func in obj) {
if (typeof(obj[func]) === 'function') {
obj[func] = _.bind(obj[func], obj);
}
}
};
/**
* @param {*=} obj
* @param {function(...[*])=} iterator
* @param {Object=} context
*/
_.each = function(obj, iterator, context) {
if (obj === null || obj === undefined) {
return;
}
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) {
return;
}
}
} else {
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) {
return;
}
}
}
}
};
_.escapeHTML = function(s) {
var escaped = s;
if (escaped && _.isString(escaped)) {
escaped = escaped
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
return escaped;
};
_.extend = function(obj) {
_.each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (source[prop] !== void 0) {
obj[prop] = source[prop];
}
}
});
return obj;
};
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) === '[object Array]';
};
// from a comment on http://dbj.org/dbj/?p=286
// fails on only one very rare and deliberate custom object:
// var bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }};
_.isFunction = function(f) {
try {
return /^\s*\bfunction\b/.test(f);
} catch (x) {
return false;
}
};
_.isArguments = function(obj) {
return !!(obj && hasOwnProperty.call(obj, 'callee'));
};
_.toArray = function(iterable) {
if (!iterable) {
return [];
}
if (iterable.toArray) {
return iterable.toArray();
}
if (_.isArray(iterable)) {
return slice.call(iterable);
}
if (_.isArguments(iterable)) {
return slice.call(iterable);
}
return _.values(iterable);
};
_.keys = function(obj) {
var results = [];
if (obj === null) {
return results;
}
_.each(obj, function(value, key) {
results[results.length] = key;
});
return results;
};
_.values = function(obj) {
var results = [];
if (obj === null) {
return results;
}
_.each(obj, function(value) {
results[results.length] = value;
});
return results;
};
_.identity = function(value) {
return value;
};
_.include = function(obj, target) {
var found = false;
if (obj === null) {
return found;
}
if (nativeIndexOf && obj.indexOf === nativeIndexOf) {
return obj.indexOf(target) != -1;
}
_.each(obj, function(value) {
if (found || (found = (value === target))) {
return breaker;
}
});
return found;
};
_.includes = function(str, needle) {
return str.indexOf(needle) !== -1;
};
// Underscore Addons
_.inherit = function(subclass, superclass) {
subclass.prototype = new superclass();
subclass.prototype.constructor = subclass;
subclass.superclass = superclass.prototype;
return subclass;
};
_.isObject = function(obj) {
return (obj === Object(obj) && !_.isArray(obj));
};
_.isEmptyObject = function(obj) {
if (_.isObject(obj)) {
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) {
return false;
}
}
return true;
}
return false;
};
_.isUndefined = function(obj) {
return obj === void 0;
};
_.isString = function(obj) {
return toString.call(obj) == '[object String]';
};
_.isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
_.isNumber = function(obj) {
return toString.call(obj) == '[object Number]';
};
_.isElement = function(obj) {
return !!(obj && obj.nodeType === 1);
};
_.encodeDates = function(obj) {
_.each(obj, function(v, k) {
if (_.isDate(v)) {
obj[k] = _.formatDate(v);
} else if (_.isObject(v)) {
obj[k] = _.encodeDates(v); // recurse
}
});
return obj;
};
_.timestamp = function() {
Date.now = Date.now || function() {
return +new Date;
};
return Date.now();
};
_.formatDate = function(d) {
// YYYY-MM-DDTHH:MM:SS in UTC
function pad(n) {
return n < 10 ? '0' + n : n;
}
return d.getUTCFullYear() + '-' +
pad(d.getUTCMonth() + 1) + '-' +
pad(d.getUTCDate()) + 'T' +
pad(d.getUTCHours()) + ':' +
pad(d.getUTCMinutes()) + ':' +
pad(d.getUTCSeconds());
};
_.safewrap = function(f) {
return function() {
try {
return f.apply(this, arguments);
} catch (e) {
console$1.critical('Implementation error. Please turn on debug and contact [email protected].');
if (Config.DEBUG){
console$1.critical(e);
}
}
};
};
_.safewrap_class = function(klass, functions) {
for (var i = 0; i < functions.length; i++) {
klass.prototype[functions[i]] = _.safewrap(klass.prototype[functions[i]]);
}
};
_.safewrap_instance_methods = function(obj) {
for (var func in obj) {
if (typeof(obj[func]) === 'function') {
obj[func] = _.safewrap(obj[func]);
}
}
};
_.strip_empty_properties = function(p) {
var ret = {};
_.each(p, function(v, k) {
if (_.isString(v) && v.length > 0) {
ret[k] = v;
}
});
return ret;
};
/*
* this function returns a copy of object after truncating it. If
* passed an Array or Object it will iterate through obj and
* truncate all the values recursively.
*/
_.truncate = function(obj, length) {
var ret;
if (typeof(obj) === 'string') {
ret = obj.slice(0, length);
} else if (_.isArray(obj)) {
ret = [];
_.each(obj, function(val) {
ret.push(_.truncate(val, length));
});
} else if (_.isObject(obj)) {
ret = {};
_.each(obj, function(val, key) {
ret[key] = _.truncate(val, length);
});
} else {
ret = obj;
}
return ret;
};
_.JSONEncode = (function() {
return function(mixed_val) {
var value = mixed_val;
var quote = function(string) {
var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; // eslint-disable-line no-control-regex
var meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"': '\\"',
'\\': '\\\\'
};
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 + '"';
};
var str = function(key, holder) {
var gap = '';
var indent = ' ';
var i = 0; // The loop counter.
var k = ''; // The member key.
var v = ''; // The member value.
var length = 0;
var mind = gap;
var partial = [];
var value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
case 'object':
// If the type is 'object', we might be dealing with an object or an array or
// null.
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// Iterate through all of the keys in the object.
for (k in value) {
if (hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{' + partial.join(',') + '' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
};
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {
'': value
});
};
})();
/**
* From https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js
* Slightly modified to throw a real Error rather than a POJO
*/
_.JSONDecode = (function() {
var at, // The index of the current character
ch, // The current character
escapee = {
'"': '"',
'\\': '\\',
'/': '/',
'b': '\b',
'f': '\f',
'n': '\n',
'r': '\r',
't': '\t'
},
text,
error = function(m) {
var e = new SyntaxError(m);
e.at = at;
e.text = text;
throw e;
},
next = function(c) {
// If a c parameter is provided, verify that it matches the current character.
if (c && c !== ch) {
error('Expected \'' + c + '\' instead of \'' + ch + '\'');
}
// Get the next character. When there are no more characters,
// return the empty string.
ch = text.charAt(at);
at += 1;
return ch;
},
number = function() {
// Parse a number value.
var number,
string = '';
if (ch === '-') {
string = '-';
next('-');
}
while (ch >= '0' && ch <= '9') {
string += ch;
next();
}
if (ch === '.') {
string += '.';
while (next() && ch >= '0' && ch <= '9') {
string += ch;
}
}
if (ch === 'e' || ch === 'E') {
string += ch;
next();
if (ch === '-' || ch === '+') {
string += ch;
next();
}
while (ch >= '0' && ch <= '9') {
string += ch;
next();
}
}
number = +string;
if (!isFinite(number)) {
error('Bad number');
} else {
return number;
}
},
string = function() {
// Parse a string value.
var hex,
i,
string = '',
uffff;
// When parsing for string values, we must look for " and \ characters.
if (ch === '"') {
while (next()) {
if (ch === '"') {
next();
return string;
}
if (ch === '\\') {
next();
if (ch === 'u') {
uffff = 0;
for (i = 0; i < 4; i += 1) {
hex = parseInt(next(), 16);
if (!isFinite(hex)) {
break;
}
uffff = uffff * 16 + hex;
}
string += String.fromCharCode(uffff);
} else if (typeof escapee[ch] === 'string') {
string += escapee[ch];
} else {
break;
}
} else {
string += ch;
}
}
}
error('Bad string');
},
white = function() {
// Skip whitespace.
while (ch && ch <= ' ') {
next();
}
},
word = function() {
// true, false, or null.
switch (ch) {
case 't':
next('t');
next('r');
next('u');
next('e');
return true;
case 'f':
next('f');
next('a');
next('l');
next('s');
next('e');
return false;
case 'n':
next('n');
next('u');
next('l');
next('l');
return null;
}
error('Unexpected "' + ch + '"');
},
value, // Placeholder for the value function.
array = function() {
// Parse an array value.
var array = [];
if (ch === '[') {
next('[');
white();
if (ch === ']') {
next(']');
return array; // empty array
}
while (ch) {
array.push(value());
white();
if (ch === ']') {
next(']');
return array;
}
next(',');
white();
}
}
error('Bad array');
},
object = function() {
// Parse an object value.
var key,
object = {};
if (ch === '{') {
next('{');
white();
if (ch === '}') {
next('}');
return object; // empty object
}
while (ch) {
key = string();
white();
next(':');
if (Object.hasOwnProperty.call(object, key)) {
error('Duplicate key "' + key + '"');
}
object[key] = value();
white();
if (ch === '}') {
next('}');
return object;
}
next(',');
white();
}
}
error('Bad object');
};
value = function() {
// Parse a JSON value. It could be an object, an array, a string,
// a number, or a word.
white();
switch (ch) {
case '{':
return object();
case '[':
return array();
case '"':
return string();
case '-':
return number();
default:
return ch >= '0' && ch <= '9' ? number() : word();
}
};
// Return the json_parse function. It will have access to all of the
// above functions and variables.
return function(source) {
var result;
text = source;
at = 0;
ch = ' ';
result = value();
white();
if (ch) {
error('Syntax error');
}
return result;
};
})();
_.base64Encode = function(data) {
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
ac = 0,
enc = '',
tmp_arr = [];
if (!data) {
return data;
}
data = _.utf8Encode(data);
do { // pack three octets into four hexets
o1 = data.charCodeAt(i++);
o2 = data.charCodeAt(i++);
o3 = data.charCodeAt(i++);
bits = o1 << 16 | o2 << 8 | o3;
h1 = bits >> 18 & 0x3f;
h2 = bits >> 12 & 0x3f;
h3 = bits >> 6 & 0x3f;
h4 = bits & 0x3f;
// use hexets to index into b64, and append result to encoded string
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
} while (i < data.length);
enc = tmp_arr.join('');
switch (data.length % 3) {
case 1:
enc = enc.slice(0, -2) + '==';
break;
case 2:
enc = enc.slice(0, -1) + '=';
break;
}
return enc;
};
_.utf8Encode = function(string) {
string = (string + '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
var utftext = '',
start,
end;
var stringl = 0,
n;
start = end = 0;
stringl = string.length;
for (n = 0; n < stringl; n++) {
var c1 = string.charCodeAt(n);
var enc = null;
if (c1 < 128) {
end++;
} else if ((c1 > 127) && (c1 < 2048)) {
enc = String.fromCharCode((c1 >> 6) | 192, (c1 & 63) | 128);
} else {
enc = String.fromCharCode((c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128);
}
if (enc !== null) {
if (end > start) {
utftext += string.substring(start, end);
}
utftext += enc;
start = end = n + 1;
}
}
if (end > start) {
utftext += string.substring(start, string.length);
}
return utftext;
};
_.UUID = (function() {
// Time/ticks information
// 1*new Date() is a cross browser version of Date.now()
var T = function() {
var d = 1 * new Date(),
i = 0;
// this while loop figures how many browser ticks go by
// before 1*new Date() returns a new number, ie the amount
// of ticks that go by per millisecond
while (d == 1 * new Date()) {
i++;
}
return d.toString(16) + i.toString(16);
};
// Math.Random entropy
var R = function() {
return Math.random().toString(16).replace('.', '');
};
// User agent entropy
// This function takes the user agent string, and then xors
// together each sequence of 8 bytes. This produces a final
// sequence of 8 bytes which it returns as hex.
var UA = function() {
var ua = userAgent,
i, ch, buffer = [],
ret = 0;
function xor(result, byte_array) {
var j, tmp = 0;
for (j = 0; j < byte_array.length; j++) {
tmp |= (buffer[j] << j * 8);
}
return result ^ tmp;
}
for (i = 0; i < ua.length; i++) {
ch = ua.charCodeAt(i);
buffer.unshift(ch & 0xFF);
if (buffer.length >= 4) {
ret = xor(ret, buffer);
buffer = [];
}
}
if (buffer.length > 0) {
ret = xor(ret, buffer);
}
return ret.toString(16);
};
return function() {
var se = (screen.height * screen.width).toString(16);
return (T() + '-' + R() + '-' + UA() + '-' + se + '-' + T());
};
})();
// _.isBlockedUA()
// This is to block various web spiders from executing our JS and
// sending false tracking data
_.isBlockedUA = function(ua) {
if (/(google web preview|baiduspider|yandexbot|bingbot|googlebot|yahoo! slurp)/i.test(ua)) {
return true;
}
return false;
};
/**
* @param {Object=} formdata
* @param {string=} arg_separator
*/
_.HTTPBuildQuery = function(formdata, arg_separator) {
var use_val, use_key, tmp_arr = [];
if (_.isUndefined(arg_separator)) {
arg_separator = '&';
}
_.each(formdata, function(val, key) {
use_val = encodeURIComponent(val.toString());
use_key = encodeURIComponent(key);
tmp_arr[tmp_arr.length] = use_key + '=' + use_val;
});
return tmp_arr.join(arg_separator);
};
_.getQueryParam = function(url, param) {
// Expects a raw URL
param = param.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]');
var regexS = '[\\?&]' + param + '=([^&#]*)',
regex = new RegExp(regexS),
results = regex.exec(url);
if (results === null || (results && typeof(results[1]) !== 'string' && results[1].length)) {
return '';
} else {
return decodeURIComponent(results[1]).replace(/\+/g, ' ');
}
};
_.getHashParam = function(hash, param) {
var matches = hash.match(new RegExp(param + '=([^&]*)'));
return matches ? matches[1] : null;
};
// _.cookie
// Methods partially borrowed from quirksmode.org/js/cookies.html
_.cookie = {
get: function(name) {
var nameEQ = name + '=';
var ca = document$1.cookie.split(';');
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(nameEQ) === 0) {
return decodeURIComponent(c.substring(nameEQ.length, c.length));
}
}
return null;
},
parse: function(name) {
var cookie;
try {
cookie = _.JSONDecode(_.cookie.get(name)) || {};
} catch (err) {
// noop
}
return cookie;
},
set_seconds: function(name, value, seconds, cross_subdomain, is_secure) {
var cdomain = '',
expires = '',
secure = '';
if (cross_subdomain) {
var matches = document$1.location.hostname.match(/[a-z0-9][a-z0-9\-]+\.[a-z\.]{2,6}$/i),
domain = matches ? matches[0] : '';
cdomain = ((domain) ? '; domain=.' + domain : '');
}
if (seconds) {
var date = new Date();
date.setTime(date.getTime() + (seconds * 1000));
expires = '; expires=' + date.toGMTString();
}
if (is_secure) {
secure = '; secure';
}
document$1.cookie = name + '=' + encodeURIComponent(value) + expires + '; path=/' + cdomain + secure;
},
set: function(name, value, days, cross_subdomain, is_secure) {
var cdomain = '', expires = '', secure = '';
if (cross_subdomain) {
var matches = document$1.location.hostname.match(/[a-z0-9][a-z0-9\-]+\.[a-z\.]{2,6}$/i),
domain = matches ? matches[0] : '';
cdomain = ((domain) ? '; domain=.' + domain : '');
}
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = '; expires=' + date.toGMTString();
}
if (is_secure) {
secure = '; secure';
}
var new_cookie_val = name + '=' + encodeURIComponent(value) + expires + '; path=/' + cdomain + secure;
document$1.cookie = new_cookie_val;
return new_cookie_val;
},
remove: function(name, cross_subdomain) {
_.cookie.set(name, '', -1, cross_subdomain);
}
};
// _.localStorage
var _localStorage_supported = null;
_.localStorage = {
is_supported: function() {
if (_localStorage_supported !== null) {
return _localStorage_supported;
}
var supported = true;
try {
var key = '__mplssupport__',
val = 'xyz';
_.localStorage.set(key, val);
if (_.localStorage.get(key) !== val) {
supported = false;
}
_.localStorage.remove(key);
} catch (err) {
supported = false;
}
if (!supported) {
console$1.error('localStorage unsupported; falling back to cookie store');
}
_localStorage_supported = supported;
return supported;
},
error: function(msg) {
console$1.error('localStorage error: ' + msg);
},
get: function(name) {
try {
return window.localStorage.getItem(name);
} catch (err) {
_.localStorage.error(err);
}
return null;
},
parse: function(name) {
try {
return _.JSONDecode(_.localStorage.get(name)) || {};
} catch (err) {
// noop
}
return null;
},
set: function(name, value) {
try {
window.localStorage.setItem(name, value);
} catch (err) {
_.localStorage.error(err);
}
},
remove: function(name) {
try {
window.localStorage.removeItem(name);
} catch (err) {
_.localStorage.error(err);
}
}
};
_.register_event = (function() {
// written by Dean Edwards, 2005
// with input from Tino Zijdel - [email protected]
// with input from Carl Sverre - [email protected]
// with input from Mixpanel
// http://dean.edwards.name/weblog/2005/10/add-event/
// https://gist.github.com/1930440
/**
* @param {Object} element
* @param {string} type
* @param {function(...[*])} handler
* @param {boolean=} oldSchool
* @param {boolean=} useCapture
*/
var register_event = function(element, type, handler, oldSchool, useCapture) {
if (!element) {
console$1.error('No valid element provided to register_event');
return;
}
if (element.addEventListener && !oldSchool) {
element.addEventListener(type, handler, !!useCapture);
} else {
var ontype = 'on' + type;
var old_handler = element[ontype]; // can be undefined
element[ontype] = makeHandler(element, handler, old_handler);
}
};
function makeHandler(element, new_handler, old_handlers) {
var handler = function(event) {
event = event || fixEvent(window.event);
// this basically happens in firefox whenever another script
// overwrites the onload callback and doesn't pass the event
// object to previously defined callbacks. All the browsers
// that don't define window.event implement addEventListener
// so the dom_loaded handler will still be fired as usual.
if (!event) {
return undefined;
}
var ret = true;
var old_result, new_result;
if (_.isFunction(old_handlers)) {
old_result = old_handlers(event);
}
new_result = new_handler.call(element, event);
if ((false === old_result) || (false === new_result)) {
ret = false;
}
return ret;
};
return handler;
}
function fixEvent(event) {
if (event) {
event.preventDefault = fixEvent.preventDefault;
event.stopPropagation = fixEvent.stopPropagation;
}
return event;
}
fixEvent.preventDefault = function() {
this.returnValue = false;
};
fixEvent.stopPropagation = function() {
this.cancelBubble = true;
};
return register_event;
})();
_.dom_query = (function() {
/* document.getElementsBySelector(selector)
- returns an array of element objects from the current document
matching the CSS selector. Selectors can contain element names,
class names and ids and can be nested. For example:
elements = document.getElementsBySelector('div#main p a.external')
Will return an array of all 'a' elements with 'external' in their
class attribute that are contained inside 'p' elements that are
contained inside the 'div' element which has id="main"
New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
See http://www.w3.org/TR/css3-selectors/#attribute-selectors
Version 0.4 - Simon Willison, March 25th 2003
-- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
-- Opera 7 fails
Version 0.5 - Carl Sverre, Jan 7th 2013
-- Now uses jQuery-esque `hasClass` for testing class name
equality. This fixes a bug related to '-' characters being
considered not part of a 'word' in regex.
*/
function getAllChildren(e) {
// Returns all children of element. Workaround required for IE5/Windows. Ugh.
return e.all ? e.all : e.getElementsByTagName('*');
}
var bad_whitespace = /[\t\r\n]/g;
function hasClass(elem, selector) {
var className = ' ' + selector + ' ';
return ((' ' + elem.className + ' ').replace(bad_whitespace, ' ').indexOf(className) >= 0);
}
function getElementsBySelector(selector) {
// Attempt to fail gracefully in lesser browsers
if (!document$1.getElementsByTagName) {
return [];
}
// Split selector in to tokens
var tokens = selector.split(' ');
var token, bits, tagName, found, foundCount, i, j, k, elements, currentContextIndex;
var currentContext = [document$1];
for (i = 0; i < tokens.length; i++) {
token = tokens[i].replace(/^\s+/, '').replace(/\s+$/, '');
if (token.indexOf('#') > -1) {
// Token is an ID selector
bits = token.split('#');
tagName = bits[0];
var id = bits[1];
var element = document$1.getElementById(id);
if (!element || (tagName && element.nodeName.toLowerCase() != tagName)) {
// element not found or tag with that ID not found, return false
return [];
}
// Set currentContext to contain just this element
currentContext = [element];
continue; // Skip to next token
}
if (token.indexOf('.') > -1) {
// Token contains a class selector
bits = token.split('.');
tagName = bits[0];
var className = bits[1];
if (!tagName) {
tagName = '*';
}
// Get elements matching tag, filter them for class selector
found = [];
foundCount = 0;
for (j = 0; j < currentContext.length; j++) {
if (tagName == '*') {
elements = getAllChildren(currentContext[j]);
} else {
elements = currentContext[j].getElementsByTagName(tagName);
}
for (k = 0; k < elements.length; k++) {
found[foundCount++] = elements[k];
}
}
currentContext = [];
currentContextIndex = 0;
for (j = 0; j < found.length; j++) {
if (found[j].className &&
_.isString(found[j].className) && // some SVG elements have classNames which are not strings
hasClass(found[j], className)
) {
currentContext[currentContextIndex++] = found[j];
}
}
continue; // Skip to next token
}
// Code to deal with attribute selectors
var token_match = token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/);
if (token_match) {
tagName = token_match[1];
var attrName = token_match[2];
var attrOperator = token_match[3];
var attrValue = token_match[4];
if (!tagName) {
tagName = '*';
}
// Grab all of the tagName elements within current context
found = [];
foundCount = 0;
for (j = 0; j < currentContext.length; j++) {
if (tagName == '*') {
elements = getAllChildren(currentContext[j]);
} else {
elements = currentContext[j].getElementsByTagName(tagName);
}
for (k = 0; k < elements.length; k++) {
found[foundCount++] = elements[k];
}
}
currentContext = [];
currentContextIndex = 0;
var checkFunction; // This function will be used to filter the elements
switch (attrOperator) {
case '=': // Equality
checkFunction = function(e) {
return (e.getAttribute(attrName) == attrValue);
};
break;
case '~': // Match one of space seperated words
checkFunction = function(e) {
return (e.getAttribute(attrName).match(new RegExp('\\b' + attrValue + '\\b')));
};
break;
case '|': // Match start with value followed by optional hyphen
checkFunction = function(e) {
return (e.getAttribute(attrName).match(new RegExp('^' + attrValue + '-?')));
};
break;
case '^': // Match starts with value
checkFunction = function(e) {
return (e.getAttribute(attrName).indexOf(attrValue) === 0);
};
break;
case '$': // Match ends with value - fails with "Warning" in Opera 7
checkFunction = function(e) {
return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length);
};
break;
case '*': // Match ends with value
checkFunction = function(e) {
return (e.getAttribute(attrName).indexOf(attrValue) > -1);
};
break;
default:
// Just test for existence of attribute
checkFunction = function(e) {
return e.getAttribute(attrName);
};
}
currentContext = [];
currentContextIndex = 0;
for (j = 0; j < found.length; j++) {
if (checkFunction(found[j])) {
currentContext[currentContextIndex++] = found[j];
}
}
// alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
continue; // Skip to next token
}
// If we get here, token is JUST an element (not a class or ID selector)
tagName = token;
found = [];
foundCount = 0;
for (j = 0; j < currentContext.length; j++) {
elements = currentContext[j].getElementsByTagName(tagName);
for (k = 0; k < elements.length; k++) {
found[foundCount++] = elements[k];
}
}
currentContext = found;
}
return currentContext;
}
return function(query) {
if (_.isElement(query)) {
return [query];
} else if (_.isObject(query) && !_.isUndefined(query.length)) {
return query;
} else {
return getElementsBySelector.call(this, query);
}
};
})();
_.info = {
campaignParams: function() {
var campaign_keywords = 'utm_source utm_medium utm_campaign utm_content utm_term'.split(' '),
kw = '',
params = {};
_.each(campaign_keywords, function(kwkey) {
kw = _.getQueryParam(document$1.URL, kwkey);
if (kw.length) {
params[kwkey] = kw;
}
});
return params;
},
searchEngine: function(referrer) {
if (referrer.search('https?://(.*)google.([^/?]*)') === 0) {
return 'google';
} else if (referrer.search('https?://(.*)bing.com') === 0) {
return 'bing';
} else if (referrer.search('https?://(.*)yahoo.com') === 0) {
return 'yahoo';
} else if (referrer.search('https?://(.*)duckduckgo.com') === 0) {
return 'duckduckgo';
} else {
return null;
}
},
searchInfo: function(referrer) {
var search = _.info.searchEngine(referrer),
param = (search != 'yahoo') ? 'q' : 'p',
ret = {};
if (search !== null) {
ret['$search_engine'] = search;
var keyword = _.getQueryParam(referrer, param);
if (keyword.length) {
ret['mp_keyword'] = keyword;
}
}
return ret;
},
/**
* This function detects which browser is running this script.
* The order of the checks are important since many user agents
* include key words used in later checks.
*/
browser: function(user_agent, vendor, opera) {
vendor = vendor || ''; // vendor is undefined for at least IE9
if (opera || _.includes(user_agent, ' OPR/')) {
if (_.includes(user_agent, 'Mini')) {
return 'Opera Mini';
}
return 'Opera';
} else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) {
return 'BlackBerry';
} else if (_.includes(user_agent, 'IEMobile') || _.includes(user_agent, 'WPDesktop')) {
return 'Internet Explorer Mobile';
} else if (_.includes(user_agent, 'Edge')) {
return 'Microsoft Edge';
} else if (_.includes(user_agent, 'FBIOS')) {
return 'Facebook Mobile';
} else if (_.includes(user_agent, 'Chrome')) {
return 'Chrome';
} else if (_.includes(user_agent, 'CriOS')) {
return 'Chrome iOS';
} else if (_.includes(user_agent, 'UCWEB') || _.includes(user_agent, 'UCBrowser')) {
return 'UC Browser';
} else if (_.includes(user_agent, 'FxiOS')) {
return 'Firefox iOS';
} else if (_.includes(vendor, 'Apple')) {
if (_.includes(user_agent, 'Mobile')) {
return 'Mobile Safari';
}
return 'Safari';
} else if (_.includes(user_agent, 'Android')) {
return 'Android Mobile';
} else if (_.includes(user_agent, 'Konqueror')) {
return 'Konqueror';
} else if (_.includes(user_agent, 'Firefox')) {
return 'Firefox';
} else if (_.includes(user_agent, 'MSIE') || _.includes(user_agent, 'Trident/')) {
return 'Internet Explorer';
} else if (_.includes(user_agent, 'Gecko')) {
return 'Mozilla';
} else {
return '';
}
},
/**
* This function detects which browser version is running this script,
* parsing major and minor version (e.g., 42.1). User agent strings from:
* http://www.useragentstring.com/pages/useragentstring.php
*/
browserVersion: function(userAgent, vendor, opera) {
var browser = _.info.browser(userAgent, vendor, opera);
var versionRegexs = {
'Internet Explorer Mobile': /rv:(\d+(\.\d+)?)/,
'Microsoft Edge': /Edge\/(\d+(\.\d+)?)/,
'Chrome': /Chrome\/(\d+(\.\d+)?)/,
'Chrome iOS': /CriOS\/(\d+(\.\d+)?)/,
'UC Browser' : /(UCBrowser|UCWEB)\/(\d+(\.\d+)?)/,
'Safari': /Version\/(\d+(\.\d+)?)/,
'Mobile Safari': /Version\/(\d+(\.\d+)?)/,
'Opera': /(Opera|OPR)\/(\d+(\.\d+)?)/,
'Firefox': /Firefox\/(\d+(\.\d+)?)/,
'Firefox iOS': /FxiOS\/(\d+(\.\d+)?)/,
'Konqueror': /Konqueror:(\d+(\.\d+)?)/,
'BlackBerry': /BlackBerry (\d+(\.\d+)?)/,
'Android Mobile': /android\s(\d+(\.\d+)?)/,
'Internet Explorer': /(rv:|MSIE )(\d+(\.\d+)?)/,
'Mozilla': /rv:(\d+(\.\d+)?)/
};
var regex = versionRegexs[browser];
if (regex === undefined) {
return null;
}
var matches = userAgent.match(regex);
if (!matches) {
return null;
}
return parseFloat(matches[matches.length - 2]);
},
os: function() {
var a = userAgent;
if (/Windows/i.test(a)) {
if (/Phone/.test(a) || /WPDesktop/.test(a)) {
return 'Windows Phone';
}
return 'Windows';
} else if (/(iPhone|iPad|iPod)/.test(a)) {
return 'iOS';
} else if (/Android/.test(a)) {
return 'Android';
} else if (/(BlackBerry|PlayBook|BB10)/i.test(a)) {
return 'BlackBerry';
} else if (/Mac/i.test(a)) {
return 'Mac OS X';
} else if (/Linux/.test(a)) {
return 'Linux';
} else if (/CrOS/.test(a)) {
return 'Chrome OS';
} else {
return '';
}
},
device: function(user_agent) {
if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) {
return 'Windows Phone';
} else if (/iPad/.test(user_agent)) {
return 'iPad';
} else if (/iPod/.test(user_agent)) {
return 'iPod Touch';
} else if (/iPhone/.test(user_agent)) {
return 'iPhone';
} else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) {
return 'BlackBerry';
} else if (/Android/.test(user_agent)) {
return 'Android';
} else {
return '';
}
},
referringDomain: function(referrer) {
var split = referrer.split('/');
if (split.length >= 3) {
return split[2];
}
return '';
},
properties: function() {
return _.extend(_.strip_empty_properties({
'$os': _.info.os(),
'$browser': _.info.browser(userAgent, navigator$1.vendor, windowOpera),
'$referrer': document$1.referrer,
'$referring_domain': _.info.referringDomain(document$1.referrer),
'$device': _.info.device(userAgent)
}), {
'$current_url': window$1.location.href,
'$browser_version': _.info.browserVersion(userAgent, navigator$1.vendor, windowOpera),
'$screen_height': screen.height,
'$screen_width': screen.width,
'mp_lib': 'web',
'$lib_version': Config.LIB_VERSION
});
},
people_properties: function() {
return _.extend(_.strip_empty_properties({
'$os': _.info.os(),
'$browser': _.info.browser(userAgent, navigator$1.vendor, windowOpera)
}), {
'$browser_version': _.info.browserVersion(userAgent, navigator$1.vendor, windowOpera)
});
},
pageviewInfo: function(page) {
return _.strip_empty_properties({
'mp_page': page,
'mp_referrer': document$1.referrer,
'mp_browser': _.info.browser(userAgent, navigator$1.vendor, windowOpera),
'mp_platform': _.info.os()
});
}
};
// EXPORTS (for closure compiler)
_['toArray'] = _.toArray;
_['isObject'] = _.isObject;
_['JSONEncode'] = _.JSONEncode;
_['JSONDecode'] = _.JSONDecode;
_['isBlockedUA'] = _.isBlockedUA;
_['isEmptyObject'] = _.isEmptyObject;
_['info'] = _.info;
_['info']['device'] = _.info.device;
_['info']['browser'] = _.info.browser;
_['info']['properties'] = _.info.properties;
/*
* Get the className of an element, accounting for edge cases where element.className is an object
* @param {Element} el - element to get the className of
* @returns {string} the element's class
*/
function getClassName(el) {
switch(typeof el.className) {
case 'string':
return el.className;
case 'object': // handle cases where className might be SVGAnimatedString or some other type
return el.className.baseVal || el.getAttribute('class') || '';
default: // future proof
return '';
}
}
/*
* Get the direct text content of an element, protecting against sensitive data collection.
* Concats textContent of each of the element's text node children; this avoids potential
* collection of sensitive data that could happen if we used element.textContent and the
* element had sensitive child elements, since element.textContent includes child content.
* Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
* @param {Element} el - element to get the text of
* @returns {string} the element's direct text content
*/
function getSafeText(el) {
var elText = '';
if (shouldTrackElement(el) && el.childNodes && el.childNodes.length) {
_.each(el.childNodes, function(child) {
if (isTextNode(child) && child.textContent) {
elText += _.trim(child.textContent)
// scrub potentially sensitive values
.split(/(\s+)/).filter(shouldTrackValue).join('')
// normalize whitespace
.replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ')
// truncate
.substring(0, 255);
}
});
}
return _.trim(elText);
}
/*
* Check whether an element has nodeType Node.ELEMENT_NODE
* @param {Element} el - element to check
* @returns {boolean} whether el is of the correct nodeType
*/
function isElementNode(el) {
return el && el.nodeType === 1; // Node.ELEMENT_NODE - use integer constant for browser portability
}
/*
* Check whether an element is of a given tag type.
* Due to potential reference discrepancies (such as the webcomponents.js polyfill),
* we want to match tagNames instead of specific references because something like
* element === document.body won't always work because element might not be a native
* element.
* @param {Element} el - element to check
* @param {string} tag - tag name (e.g., "div")
* @returns {boolean} whether el is of the given tag type
*/
function isTag(el, tag) {
return el && el.tagName && el.tagName.toLowerCase() === tag.toLowerCase();
}
/*
* Check whether an element has nodeType Node.TEXT_NODE
* @param {Element} el - element to check
* @returns {boolean} whether el is of the correct nodeType
*/
function isTextNode(el) {
return el && el.nodeType === 3; // Node.TEXT_NODE - use integer constant for browser portability
}
/*
* Check whether a DOM event should be "tracked" or if it may contain sentitive data
* using a variety of heuristics.
* @param {Element} el - element to check
* @param {Event} event - event to check
* @returns {boolean} whether the event should be tracked
*/
function shouldTrackDomEvent(el, event) {
if (!el || isTag(el, 'html') || !isElementNode(el)) {
return false;
}
var tag = el.tagName.toLowerCase();
switch (tag) {
case 'html':
return false;
case 'form':
return event.type === 'submit';
case 'input':
if (['button', 'submit'].indexOf(el.getAttribute('type')) === -1) {
return event.type === 'change';
} else {
return event.type === 'click';
}
case 'select':
case 'textarea':
return event.type === 'change';
default:
return event.type === 'click';
}
}
/*
* Check whether a DOM element should be "tracked" or if it may contain sentitive data
* using a variety of heuristics.
* @param {Element} el - element to check
* @returns {boolean} whether the element should be tracked
*/
function shouldTrackElement(el) {
for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
var classes = getClassName(curEl).split(' ');
if (_.includes(classes, 'mp-sensitive') || _.includes(classes, 'mp-no-track')) {
return false;
}
}
if (_.includes(getClassName(el).split(' '), 'mp-include')) {
return true;
}
// don't send data from inputs or similar elements since there will always be
// a risk of clientside javascript placing sensitive data in attributes
if (
isTag(el, 'input') ||
isTag(el, 'select') ||
isTag(el, 'textarea') ||
el.getAttribute('contenteditable') === 'true'
) {
return false;
}
// don't include hidden or password fields
var type = el.type || '';
if (typeof type === 'string') { // it's possible for el.type to be a DOM element if el is a form with a child input[name="type"]
switch(type.toLowerCase()) {
case 'hidden':
return false;
case 'password':
return false;
}
}
// filter out data from fields that look like sensitive fields
var name = el.name || el.id || '';
if (typeof name === 'string') { // it's possible for el.name or el.id to be a DOM element if el is a form with a child input[name="name"]
var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
return false;
}
}
return true;
}
/*
* Check whether a string value should be "tracked" or if it may contain sentitive data
* using a variety of heuristics.
* @param {string} value - string value to check
* @returns {boolean} whether the element should be tracked
*/
function shouldTrackValue(value) {
if (value === null || _.isUndefined(value)) {
return false;
}
if (typeof value === 'string') {
value = _.trim(value);
// check to see if input value looks like a credit card number
// see: https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s20.html
var ccRegex = /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/;
if (ccRegex.test((value || '').replace(/[\- ]/g, ''))) {
return false;
}
// check to see if input value looks like a social security number
var ssnRegex = /(^\d{3}-?\d{2}-?\d{4}$)/;
if (ssnRegex.test(value)) {
return false;
}
}
return true;
}
var autotrack = {
_initializedTokens: [],
_previousElementSibling: function(el) {
if (el.previousElementSibling) {
return el.previousElementSibling;
} else {
do {
el = el.previousSibling;
} while (el && !isElementNode(el));
return el;
}
},
_loadScript: function(scriptUrlToLoad, callback) {
var scriptTag = document.createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.src = scriptUrlToLoad;
scriptTag.onload = callback;
var scripts = document.getElementsByTagName('script');
if (scripts.length > 0) {
scripts[0].parentNode.insertBefore(scriptTag, scripts[0]);
} else {
document.body.appendChild(scriptTag);
}
},
_getPropertiesFromElement: function(elem) {
var props = {
'classes': getClassName(elem).split(' '),
'tag_name': elem.tagName.toLowerCase()
};
if (shouldTrackElement(elem)) {
_.each(elem.attributes, function(attr) {
if (shouldTrackValue(attr.value)) {
props['attr__' + attr.name] = attr.value;
}
});
}
var nthChild = 1;
var nthOfType = 1;
var currentElem = elem;
while (currentElem = this._previousElementSibling(currentElem)) { // eslint-disable-line no-cond-assign
nthChild++;
if (currentElem.tagName === elem.tagName) {
nthOfType++;
}
}
props['nth_child'] = nthChild;
props['nth_of_type'] = nthOfType;
return props;
},
_getDefaultProperties: function(eventType) {
return {
'$event_type': eventType,
'$ce_version': 1,
'$host': window.location.host,
'$pathname': window.location.pathname
};
},
_extractCustomPropertyValue: function(customProperty) {
var propValues = [];
_.each(document.querySelectorAll(customProperty['css_selector']), function(matchedElem) {
var value;
if (['input', 'select'].indexOf(matchedElem.tagName.toLowerCase()) > -1) {
value = matchedElem['value'];
} else if (matchedElem['textContent']) {
value = matchedElem['textContent'];
}
if (shouldTrackValue(value)) {
propValues.push(value);
}
});
return propValues.join(', ');
},
_getCustomProperties: function(targetElementList) {
var props = {};
_.each(this._customProperties, function(customProperty) {
_.each(customProperty['event_selectors'], function(eventSelector) {
var eventElements = document.querySelectorAll(eventSelector);
_.each(eventElements, function(eventElement) {
if (_.includes(targetElementList, eventElement) && shouldTrackElement(eventElement)) {
props[customProperty['name']] = this._extractCustomPropertyValue(customProperty);
}
}, this);
}, this);
}, this);
return props;
},
_getEventTarget: function(e) {
// https://developer.mozilla.org/en-US/docs/Web/API/Event/target#Compatibility_notes
if (typeof e.target === 'undefined') {
return e.srcElement;
} else {
return e.target;
}
},
_trackEvent: function(e, instance) {
/*** Don't mess with this code without running IE8 tests on it ***/
var target = this._getEventTarget(e);
if (isTextNode(target)) { // defeat Safari bug (see: http://www.quirksmode.org/js/events_properties.html)
target = target.parentNode;
}
if (shouldTrackDomEvent(target, e)) {
var targetElementList = [target];
var curEl = target;
while (curEl.parentNode && !isTag(curEl, 'body')) {
targetElementList.push(curEl.parentNode);
curEl = curEl.parentNode;
}
var elementsJson = [];
var href, explicitNoTrack = false;
_.each(targetElementList, function(el) {
var shouldTrackEl = shouldTrackElement(el);
// if the element or a parent element is an anchor tag
// include the href as a property
if (el.tagName.toLowerCase() === 'a') {
href = el.getAttribute('href');
href = shouldTrackEl && shouldTrackValue(href) && href;
}
// allow users to programatically prevent tracking of elements by adding class 'mp-no-track'
var classes = getClassName(el).split(' ');
if (_.includes(classes, 'mp-no-track')) {
explicitNoTrack = true;
}
elementsJson.push(this._getPropertiesFromElement(el));
}, this);
if (explicitNoTrack) {
return false;
}
// only populate text content from target element (not parents)
// to prevent text within a sensitive element from being collected
// as part of a parent's el.textContent
var elementText;
var safeElementText = getSafeText(target);
if (safeElementText && safeElementText.length) {
elementText = safeElementText;
}
var props = _.extend(
this._getDefaultProperties(e.type),
{
'$elements': elementsJson,
'$el_attr__href': href,
'$el_text': elementText
},
this._getCustomProperties(targetElementList)
);
instance.track('$web_event', props);
return true;
}
},
// only reason is to stub for unit tests
// since you can't override window.location props
_navigate: function(href) {
window.location.href = href;
},
_addDomEventHandlers: function(instance) {
var handler = _.bind(function(e) {
e = e || window.event;
this._trackEvent(e, instance);
}, this);
_.register_event(document, 'submit', handler, false, true);
_.register_event(document, 'change', handler, false, true);
_.register_event(document, 'click', handler, false, true);
},
_customProperties: {},
init: function(instance) {
if (!(document && document.body)) {
console.log('document not ready yet, trying again in 500 milliseconds...');
var that = this;
setTimeout(function() { that.init(instance); }, 500);
return;
}
var token = instance.get_config('token');
if (this._initializedTokens.indexOf(token) > -1) {
console.log('autotrack already initialized for token "' + token + '"');
return;
}
this._initializedTokens.push(token);
if (!this._maybeLoadEditor(instance)) { // don't autotrack actions when the editor is enabled
var parseDecideResponse = _.bind(function(response) {
if (response && response['config'] && response['config']['enable_collect_everything'] === true) {
if (response['custom_properties']) {
this._customProperties = response['custom_properties'];
}
instance.track('$web_event', _.extend({
'$title': document.title
}, this._getDefaultProperties('pageview')));
this._addDomEventHandlers(instance);
} else {
instance['__autotrack_enabled'] = false;
}
}, this);
instance._send_request(
instance.get_config('api_host') + '/decide/', {
'verbose': true,
'version': '1',
'lib': 'web',
'token': token
},
instance._prepare_callback(parseDecideResponse)
);
}
},
_editorParamsFromHash: function(instance, hash) {
var editorParams;
try {
var state = _.getHashParam(hash, 'state');
state = JSON.parse(decodeURIComponent(state));
var expiresInSeconds = _.getHashParam(hash, 'expires_in');
editorParams = {
'accessToken': _.getHashParam(hash, 'access_token'),
'accessTokenExpiresAt': (new Date()).getTime() + (Number(expiresInSeconds) * 1000),
'bookmarkletMode': !!state['bookmarkletMode'],
'projectId': state['projectId'],
'projectOwnerId': state['projectOwnerId'],
'projectToken': state['token'],
'readOnly': state['readOnly'],
'userFlags': state['userFlags'],
'userId': state['userId']
};
window.sessionStorage.setItem('editorParams', JSON.stringify(editorParams));
if (state['desiredHash']) {
window.location.hash = state['desiredHash'];
} else if (window.history) {
history.replaceState('', document.title, window.location.pathname + window.location.search); // completely remove hash
} else {
window.location.hash = ''; // clear hash (but leaves # unfortunately)
}
} catch (e) {
console.error('Unable to parse data from hash', e);
}
return editorParams;
},
/**
* To load the visual editor, we need an access token and other state. That state comes from one of three places:
* 1. In the URL hash params if the customer is using an old snippet
* 2. From session storage under the key `_mpcehash` if the snippet already parsed the hash
* 3. From session storage under the key `editorParams` if the editor was initialized on a previous page
*/
_maybeLoadEditor: function(instance) {
try {
var parseFromUrl = false;
if (_.getHashParam(window.location.hash, 'state')) {
var state = _.getHashParam(window.location.hash, 'state');
state = JSON.parse(decodeURIComponent(state));
parseFromUrl = state['action'] === 'mpeditor';
}
var parseFromStorage = !!window.sessionStorage.getItem('_mpcehash');
var editorParams;
if (parseFromUrl) { // happens if they are initializing the editor using an old snippet
editorParams = this._editorParamsFromHash(instance, window.location.hash);
} else if (parseFromStorage) { // happens if they are initialized the editor and using the new snippet
editorParams = this._editorParamsFromHash(instance, window.sessionStorage.getItem('_mpcehash'));
window.sessionStorage.removeItem('_mpcehash');
} else { // get credentials from sessionStorage from a previous initialzation
editorParams = JSON.parse(window.sessionStorage.getItem('editorParams') || '{}');
}
if (editorParams['projectToken'] && instance.get_config('token') === editorParams['projectToken']) {
this._loadEditor(instance, editorParams);
return true;
} else {
return false;
}
} catch (e) {
return false;
}
},
_loadEditor: function(instance, editorParams) {
if (!window['_mpEditorLoaded']) { // only load the codeless event editor once, even if there are multiple instances of MixpanelLib
window['_mpEditorLoaded'] = true;
var editorUrl = instance.get_config('app_host')
+ '/js-bundle/reports/collect-everything/editor.js?_ts='
+ (new Date()).getTime();
this._loadScript(editorUrl, function() {
window['mp_load_editor'](editorParams);
});
return true;
}
return false;
},
// this is a mechanism to ramp up CE with no server-side interaction.
// when CE is active, every page load results in a decide request. we
// need to gently ramp this up so we don't overload decide. this decides
// deterministically if CE is enabled for this project by modding the char
// value of the project token.
enabledForProject: function(token, numBuckets, numEnabledBuckets) {
numBuckets = !_.isUndefined(numBuckets) ? numBuckets : 10;
numEnabledBuckets = !_.isUndefined(numEnabledBuckets) ? numEnabledBuckets : 10;
var charCodeSum = 0;
for (var i = 0; i < token.length; i++) {
charCodeSum += token.charCodeAt(i);
}
return (charCodeSum % numBuckets) < numEnabledBuckets;
},
isBrowserSupported: function() {
return _.isFunction(document.querySelectorAll);
}
};
_.bind_instance_methods(autotrack);
_.safewrap_instance_methods(autotrack);
/**
* A function used to track a Mixpanel event (e.g. MixpanelLib.track)
* @callback trackFunction
* @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc.
* @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself.
* @param {Function} [callback] If provided, the callback function will be called after tracking the event.
*/
/** Public **/
var GDPR_DEFAULT_PERSISTENCE_PREFIX = '__mp_opt_in_out_';
/**
* Opt the user in to data tracking and cookies/localstorage for the given token
* @param {string} token - Mixpanel project tracking token
* @param {Object} [options]
* @param {trackFunction} [options.track] - function used for tracking a Mixpanel event to record the opt-in action
* @param {string} [options.trackEventName] - event name to be used for tracking the opt-in action
* @param {Object} [options.trackProperties] - set of properties to be tracked along with the opt-in action
* @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
* @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires
* @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not
* @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not
*/
function optIn(token, options) {
_optInOut(true, token, options);
}
/**
* Opt the user out of data tracking and cookies/localstorage for the given token
* @param {string} token - Mixpanel project tracking token
* @param {Object} [options]
* @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
* @param {Number} [options.cookieExpiration] - number of days until the opt-out cookie expires
* @param {boolean} [options.crossSubdomainCookie] - whether the opt-out cookie is set as cross-subdomain or not
* @param {boolean} [options.secureCookie] - whether the opt-out cookie is set as secure or not
*/
function optOut(token, options) {
_optInOut(false, token, options);
}
/**
* Check whether the user has opted in to data tracking and cookies/localstorage for the given token
* @param {string} token - Mixpanel project tracking token
* @param {Object} [options]
* @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
* @returns {boolean} whether the user has opted in to the given opt type
*/
function hasOptedIn(token, options) {
return _getStorageValue(token, options) === '1';
}
/**
* Check whether the user has opted out of data tracking and cookies/localstorage for the given token
* @param {string} token - Mixpanel project tracking token
* @param {Object} [options]
* @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
* @returns {boolean} whether the user has opted out of the given opt type
*/
function hasOptedOut(token, options) {
if (_hasDoNotTrackFlagOn()) {
return true;
}
return _getStorageValue(token, options) === '0';
}
/**
* Wrap a MixpanelLib method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token
* If the user has opted out, return early instead of executing the method.
* If a callback argument was provided, execute it passing the 0 error code.
* @param {function} method - wrapped method to be executed if the user has not opted out
* @returns {*} the result of executing method OR undefined if the user has opted out
*/
function addOptOutCheckMixpanelLib(method) {
return _addOptOutCheck(method, function(name) {
return this.get_config(name);
});
}
/**
* Wrap a MixpanelPeople method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token
* If the user has opted out, return early instead of executing the method.
* If a callback argument was provided, execute it passing the 0 error code.
* @param {function} method - wrapped method to be executed if the user has not opted out
* @returns {*} the result of executing method OR undefined if the user has opted out
*/
function addOptOutCheckMixpanelPeople(method) {
return _addOptOutCheck(method, function(name) {
return this._get_config(name);
});
}
/**
* Clear the user's opt in/out status of data tracking and cookies/localstorage for the given token
* @param {string} token - Mixpanel project tracking token
* @param {Object} [options]
* @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
* @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires
* @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not
* @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not
*/
function clearOptInOut(token, options) {
options = options || {};
_getStorage(options).remove(_getStorageKey(token, options), !!options.crossSubdomainCookie);
}
/** Private **/
/**
* Get storage util
* @param {Object} [options]
* @param {string} [options.persistenceType]
* @returns {object} either _.cookie or _.localstorage
*/
function _getStorage(options) {
options = options || {};
return options.persistenceType === 'localStorage' ? _.localStorage : _.cookie;
}
/**
* Get the name of the cookie that is used for the given opt type (tracking, cookie, etc.)
* @param {string} token - Mixpanel project tracking token
* @param {Object} [options]
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
* @returns {string} the name of the cookie for the given opt type
*/
function _getStorageKey(token, options) {
options = options || {};
return (options.persistencePrefix || GDPR_DEFAULT_PERSISTENCE_PREFIX) + token;
}
/**
* Get the value of the cookie that is used for the given opt type (tracking, cookie, etc.)
* @param {string} token - Mixpanel project tracking token
* @param {Object} [options]
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
* @returns {string} the value of the cookie for the given opt type
*/
function _getStorageValue(token, options) {
return _getStorage(options).get(_getStorageKey(token, options));
}
/**
* Check whether the user has set the DNT/doNotTrack setting to true in their browser
* @returns {boolean} whether the DNT setting is true
*/
function _hasDoNotTrackFlagOn() {
return !!(window$1.navigator && window$1.navigator.doNotTrack === '1');
}
/**
* Set cookie/localstorage for the user indicating that they are opted in or out for the given opt type
* @param {boolean} optValue - whether to opt the user in or out for the given opt type
* @param {string} token - Mixpanel project tracking token
* @param {Object} [options]
* @param {trackFunction} [options.track] - function used for tracking a Mixpanel event to record the opt-in action
* @param {string} [options.trackEventName] - event name to be used for tracking the opt-in action
* @param {Object} [options.trackProperties] - set of properties to be tracked along with the opt-in action
* @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name
* @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires
* @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not
* @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not
*/
function _optInOut(optValue, token, options) {
if (!_.isString(token) || !token.length) {
console.error('gdpr.' + (optValue ? 'optIn' : 'optOut') + ' called with an invalid token');
return;
}
options = options || {};
_getStorage(options).set(
_getStorageKey(token, options),
optValue ? 1 : 0,
_.isNumber(options.cookieExpiration) ? options.cookieExpiration : null,
!!options.crossSubdomainCookie,
!!options.secureCookie
);
if (options.track && optValue) { // only track event if opting in (optValue=true)
options.track(options.trackEventName || '$opt_in', options.trackProperties);
}
}
/**
* Wrap a method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token
* If the user has opted out, return early instead of executing the method.
* If a callback argument was provided, execute it passing the 0 error code.
* @param {function} method - wrapped method to be executed if the user has not opted out
* @param {function} getConfigValue - getter function for the Mixpanel API token and other options to be used with opt-out check
* @returns {*} the result of executing method OR undefined if the user has opted out
*/
function _addOptOutCheck(method, getConfigValue) {
return function() {
var optedOut = false;
try {
var token = getConfigValue.call(this, 'token');
var persistenceType = getConfigValue.call(this, 'opt_out_tracking_persistence_type');
var persistencePrefix = getConfigValue.call(this, 'opt_out_tracking_cookie_prefix');
if (token) { // if there was an issue getting the token, continue method execution as normal
optedOut = hasOptedOut(token, {
persistenceType: persistenceType,
persistencePrefix: persistencePrefix
});
}
} catch(err) {
console.error('Unexpected error when checking tracking opt-out status: ' + err);
}
if (!optedOut) {
return method.apply(this, arguments);
}
var callback = arguments[arguments.length - 1];
if (typeof(callback) === 'function') {
callback(0);
}
return;
};
}
/*
* Mixpanel JS Library
*
* Copyright 2012, Mixpanel, Inc. All Rights Reserved
* http://mixpanel.com/
*
* Includes portions of Underscore.js
* http://documentcloud.github.com/underscore/
* (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
* Released under the MIT License.
*/
// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @output_file_name mixpanel-2.8.min.js
// ==/ClosureCompiler==
/*
SIMPLE STYLE GUIDE:
this.x === public function
this._x === internal - only use within this file
this.__x === private - only use within the class
Globals should be all caps
*/
var init_type; // MODULE or SNIPPET loader
var mixpanel_master; // main mixpanel instance / object
var INIT_MODULE = 0;
var INIT_SNIPPET = 1;
/*
* Constants
*/
/** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
/** @const */ var SET_QUEUE_KEY = '__mps';
/** @const */ var SET_ONCE_QUEUE_KEY = '__mpso';
/** @const */ var UNSET_QUEUE_KEY = '__mpus';
/** @const */ var ADD_QUEUE_KEY = '__mpa';
/** @const */ var APPEND_QUEUE_KEY = '__mpap';
/** @const */ var UNION_QUEUE_KEY = '__mpu';
/** @const */ var SET_ACTION = '$set';
/** @const */ var SET_ONCE_ACTION = '$set_once';
/** @const */ var UNSET_ACTION = '$unset';
/** @const */ var ADD_ACTION = '$add';
/** @const */ var APPEND_ACTION = '$append';
/** @const */ var UNION_ACTION = '$union';
// This key is deprecated, but we want to check for it to see whether aliasing is allowed.
/** @const */ var PEOPLE_DISTINCT_ID_KEY = '$people_distinct_id';
/** @const */ var ALIAS_ID_KEY = '__alias';
/** @const */ var CAMPAIGN_IDS_KEY = '__cmpns';
/** @const */ var EVENT_TIMERS_KEY = '__timers';
/** @const */ var RESERVED_PROPERTIES = [
SET_QUEUE_KEY,
SET_ONCE_QUEUE_KEY,
UNSET_QUEUE_KEY,
ADD_QUEUE_KEY,
APPEND_QUEUE_KEY,
UNION_QUEUE_KEY,
PEOPLE_DISTINCT_ID_KEY,
ALIAS_ID_KEY,
CAMPAIGN_IDS_KEY,
EVENT_TIMERS_KEY
];
/*
* Dynamic... constants? Is that an oxymoron?
*/
// http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/
// https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#withCredentials
var USE_XHR = (window$1.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest());
// IE<10 does not support cross-origin XHR's but script tags
// with defer won't block window.onload; ENQUEUE_REQUESTS
// should only be true for Opera<12
var ENQUEUE_REQUESTS = !USE_XHR && (userAgent.indexOf('MSIE') === -1) && (userAgent.indexOf('Mozilla') === -1);
/*
* Module-level globals
*/
var DEFAULT_CONFIG = {
'api_host': 'https://api.mixpanel.com',
'app_host': 'https://mixpanel.com',
'autotrack': true,
'cdn': 'https://cdn.mxpnl.com',
'cross_subdomain_cookie': true,
'persistence': 'cookie',
'persistence_name': '',
'cookie_name': '',
'loaded': function() {},
'store_google': true,
'save_referrer': true,
'test': false,
'verbose': false,
'img': false,
'track_pageview': true,
'debug': false,
'track_links_timeout': 300,
'cookie_expiration': 365,
'upgrade': false,
'disable_persistence': false,
'disable_cookie': false,
'secure_cookie': false,
'ip': true,
'opt_out_tracking_by_default': false,
'opt_out_tracking_persistence_type': 'localStorage',
'opt_out_tracking_cookie_prefix': null,
'property_blacklist': [],
'xhr_headers': {} // { header: value, header2: value }
};
var DOM_LOADED = false;
/**
* DomTracker Object
* @constructor
*/
var DomTracker = function() {};
// interface
DomTracker.prototype.create_properties = function() {};
DomTracker.prototype.event_handler = function() {};
DomTracker.prototype.after_track_handler = function() {};
DomTracker.prototype.init = function(mixpanel_instance) {
this.mp = mixpanel_instance;
return this;
};
/**
* @param {Object|string} query
* @param {string} event_name
* @param {Object=} properties
* @param {function(...[*])=} user_callback
*/
DomTracker.prototype.track = function(query, event_name, properties, user_callback) {
var that = this;
var elements = _.dom_query(query);
if (elements.length === 0) {
console$1.error('The DOM query (' + query + ') returned 0 elements');
return;
}
_.each(elements, function(element) {
_.register_event(element, this.override_event, function(e) {
var options = {};
var props = that.create_properties(properties, this);
var timeout = that.mp.get_config('track_links_timeout');
that.event_handler(e, this, options);
// in case the mixpanel servers don't get back to us in time
window$1.setTimeout(that.track_callback(user_callback, props, options, true), timeout);
// fire the tracking event
that.mp.track(event_name, props, that.track_callback(user_callback, props, options));
});
}, this);
return true;
};
/**
* @param {function(...[*])} user_callback
* @param {Object} props
* @param {boolean=} timeout_occured
*/
DomTracker.prototype.track_callback = function(user_callback, props, options, timeout_occured) {
timeout_occured = timeout_occured || false;
var that = this;
return function() {
// options is referenced from both callbacks, so we can have
// a 'lock' of sorts to ensure only one fires
if (options.callback_fired) { return; }
options.callback_fired = true;
if (user_callback && user_callback(timeout_occured, props) === false) {
// user can prevent the default functionality by
// returning false from their callback
return;
}
that.after_track_handler(props, options, timeout_occured);
};
};
DomTracker.prototype.create_properties = function(properties, element) {
var props;
if (typeof(properties) === 'function') {
props = properties(element);
} else {
props = _.extend({}, properties);
}
return props;
};
/**
* LinkTracker Object
* @constructor
* @extends DomTracker
*/
var LinkTracker = function() {
this.override_event = 'click';
};
_.inherit(LinkTracker, DomTracker);
LinkTracker.prototype.create_properties = function(properties, element) {
var props = LinkTracker.superclass.create_properties.apply(this, arguments);
if (element.href) { props['url'] = element.href; }
return props;
};
LinkTracker.prototype.event_handler = function(evt, element, options) {
options.new_tab = (
evt.which === 2 ||
evt.metaKey ||
evt.ctrlKey ||
element.target === '_blank'
);
options.href = element.href;
if (!options.new_tab) {
evt.preventDefault();
}
};
LinkTracker.prototype.after_track_handler = function(props, options) {
if (options.new_tab) { return; }
setTimeout(function() {
window$1.location = options.href;
}, 0);
};
/**
* FormTracker Object
* @constructor
* @extends DomTracker
*/
var FormTracker = function() {
this.override_event = 'submit';
};
_.inherit(FormTracker, DomTracker);
FormTracker.prototype.event_handler = function(evt, element, options) {
options.element = element;
evt.preventDefault();
};
FormTracker.prototype.after_track_handler = function(props, options) {
setTimeout(function() {
options.element.submit();
}, 0);
};
/**
* Mixpanel Persistence Object
* @constructor
*/
var MixpanelPersistence = function(config) {
this['props'] = {};
this.campaign_params_saved = false;
if (config['persistence_name']) {
this.name = 'mp_' + config['persistence_name'];
} else {
this.name = 'mp_' + config['token'] + '_mixpanel';
}
var storage_type = config['persistence'];
if (storage_type !== 'cookie' && storage_type !== 'localStorage') {
console$1.critical('Unknown persistence type ' + storage_type + '; falling back to cookie');
storage_type = config['persistence'] = 'cookie';
}
if (storage_type === 'localStorage' && _.localStorage.is_supported()) {
this.storage = _.localStorage;
} else {
this.storage = _.cookie;
}
this.load();
this.update_config(config);
this.upgrade(config);
this.save();
};
MixpanelPersistence.prototype.properties = function() {
var p = {};
// Filter out reserved properties
_.each(this['props'], function(v, k) {
if (!_.include(RESERVED_PROPERTIES, k)) {
p[k] = v;
}
});
return p;
};
MixpanelPersistence.prototype.load = function() {
if (this.disabled) { return; }
var entry = this.storage.parse(this.name);
if (entry) {
this['props'] = _.extend({}, entry);
}
};
MixpanelPersistence.prototype.upgrade = function(config) {
var upgrade_from_old_lib = config['upgrade'],
old_cookie_name,
old_cookie;
if (upgrade_from_old_lib) {
old_cookie_name = 'mp_super_properties';
// Case where they had a custom cookie name before.
if (typeof(upgrade_from_old_lib) === 'string') {
old_cookie_name = upgrade_from_old_lib;
}
old_cookie = this.storage.parse(old_cookie_name);
// remove the cookie
this.storage.remove(old_cookie_name);
this.storage.remove(old_cookie_name, true);
if (old_cookie) {
this['props'] = _.extend(
this['props'],
old_cookie['all'],
old_cookie['events']
);
}
}
if (!config['cookie_name'] && config['name'] !== 'mixpanel') {
// special case to handle people with cookies of the form
// mp_TOKEN_INSTANCENAME from the first release of this library
old_cookie_name = 'mp_' + config['token'] + '_' + config['name'];
old_cookie = this.storage.parse(old_cookie_name);
if (old_cookie) {
this.storage.remove(old_cookie_name);
this.storage.remove(old_cookie_name, true);
// Save the prop values that were in the cookie from before -
// this should only happen once as we delete the old one.
this.register_once(old_cookie);
}
}
if (this.storage === _.localStorage) {
old_cookie = _.cookie.parse(this.name);
_.cookie.remove(this.name);
_.cookie.remove(this.name, true);
if (old_cookie) {
this.register_once(old_cookie);
}
}
};
MixpanelPersistence.prototype.save = function() {
if (this.disabled) { return; }
this._expire_notification_campaigns();
this.storage.set(
this.name,
_.JSONEncode(this['props']),
this.expire_days,
this.cross_subdomain,
this.secure
);
};
MixpanelPersistence.prototype.remove = function() {
// remove both domain and subdomain cookies
this.storage.remove(this.name, false);
this.storage.remove(this.name, true);
};
// removes the storage entry and deletes all loaded data
// forced name for tests
MixpanelPersistence.prototype.clear = function() {
this.remove();
this['props'] = {};
};
/**
* @param {Object} props
* @param {*=} default_value
* @param {number=} days
*/
MixpanelPersistence.prototype.register_once = function(props, default_value, days) {
if (_.isObject(props)) {
if (typeof(default_value) === 'undefined') { default_value = 'None'; }
this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days;
_.each(props, function(val, prop) {
if (!this['props'].hasOwnProperty(prop) || this['props'][prop] === default_value) {
this['props'][prop] = val;
}
}, this);
this.save();
return true;
}
return false;
};
/**
* @param {Object} props
* @param {number=} days
*/
MixpanelPersistence.prototype.register = function(props, days) {
if (_.isObject(props)) {
this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days;
_.extend(this['props'], props);
this.save();
return true;
}
return false;
};
MixpanelPersistence.prototype.unregister = function(prop) {
if (prop in this['props']) {
delete this['props'][prop];
this.save();
}
};
MixpanelPersistence.prototype._expire_notification_campaigns = _.safewrap(function() {
var campaigns_shown = this['props'][CAMPAIGN_IDS_KEY],
EXPIRY_TIME = Config.DEBUG ? 60 * 1000 : 60 * 60 * 1000; // 1 minute (Config.DEBUG) / 1 hour (PDXN)
if (!campaigns_shown) {
return;
}
for (var campaign_id in campaigns_shown) {
if (1 * new Date() - campaigns_shown[campaign_id] > EXPIRY_TIME) {
delete campaigns_shown[campaign_id];
}
}
if (_.isEmptyObject(campaigns_shown)) {
delete this['props'][CAMPAIGN_IDS_KEY];
}
});
MixpanelPersistence.prototype.update_campaign_params = function() {
if (!this.campaign_params_saved) {
this.register_once(_.info.campaignParams());
this.campaign_params_saved = true;
}
};
MixpanelPersistence.prototype.update_search_keyword = function(referrer) {
this.register(_.info.searchInfo(referrer));
};
// EXPORTED METHOD, we test this directly.
MixpanelPersistence.prototype.update_referrer_info = function(referrer) {
// If referrer doesn't exist, we want to note the fact that it was type-in traffic.
this.register_once({
'$initial_referrer': referrer || '$direct',
'$initial_referring_domain': _.info.referringDomain(referrer) || '$direct'
}, '');
};
MixpanelPersistence.prototype.get_referrer_info = function() {
return _.strip_empty_properties({
'$initial_referrer': this['props']['$initial_referrer'],
'$initial_referring_domain': this['props']['$initial_referring_domain']
});
};
// safely fills the passed in object with stored properties,
// does not override any properties defined in both
// returns the passed in object
MixpanelPersistence.prototype.safe_merge = function(props) {
_.each(this['props'], function(val, prop) {
if (!(prop in props)) {
props[prop] = val;
}
});
return props;
};
MixpanelPersistence.prototype.update_config = function(config) {
this.default_expiry = this.expire_days = config['cookie_expiration'];
this.set_disabled(config['disable_persistence']);
this.set_cross_subdomain(config['cross_subdomain_cookie']);
this.set_secure(config['secure_cookie']);
};
MixpanelPersistence.prototype.set_disabled = function(disabled) {
this.disabled = disabled;
if (this.disabled) {
this.remove();
} else {
this.save();
}
};
MixpanelPersistence.prototype.set_cross_subdomain = function(cross_subdomain) {
if (cross_subdomain !== this.cross_subdomain) {
this.cross_subdomain = cross_subdomain;
this.remove();
this.save();
}
};
MixpanelPersistence.prototype.get_cross_subdomain = function() {
return this.cross_subdomain;
};
MixpanelPersistence.prototype.set_secure = function(secure) {
if (secure !== this.secure) {
this.secure = secure ? true : false;
this.remove();
this.save();
}
};
MixpanelPersistence.prototype._add_to_people_queue = function(queue, data) {
var q_key = this._get_queue_key(queue),
q_data = data[queue],
set_q = this._get_or_create_queue(SET_ACTION),
set_once_q = this._get_or_create_queue(SET_ONCE_ACTION),
unset_q = this._get_or_create_queue(UNSET_ACTION),
add_q = this._get_or_create_queue(ADD_ACTION),
union_q = this._get_or_create_queue(UNION_ACTION),
append_q = this._get_or_create_queue(APPEND_ACTION, []);
if (q_key === SET_QUEUE_KEY) {
// Update the set queue - we can override any existing values
_.extend(set_q, q_data);
// if there was a pending increment, override it
// with the set.
this._pop_from_people_queue(ADD_ACTION, q_data);
// if there was a pending union, override it
// with the set.
this._pop_from_people_queue(UNION_ACTION, q_data);
this._pop_from_people_queue(UNSET_ACTION, q_data);
} else if (q_key === SET_ONCE_QUEUE_KEY) {
// only queue the data if there is not already a set_once call for it.
_.each(q_data, function(v, k) {
if (!(k in set_once_q)) {
set_once_q[k] = v;
}
});
this._pop_from_people_queue(UNSET_ACTION, q_data);
} else if (q_key === UNSET_QUEUE_KEY) {
_.each(q_data, function(prop) {
// undo previously-queued actions on this key
_.each([set_q, set_once_q, add_q, union_q], function(enqueued_obj) {
if (prop in enqueued_obj) {
delete enqueued_obj[prop];
}
});
_.each(append_q, function(append_obj) {
if (prop in append_obj) {
delete append_obj[prop];
}
});
unset_q[prop] = true;
});
} else if (q_key === ADD_QUEUE_KEY) {
_.each(q_data, function(v, k) {
// If it exists in the set queue, increment
// the value
if (k in set_q) {
set_q[k] += v;
} else {
// If it doesn't exist, update the add
// queue
if (!(k in add_q)) {
add_q[k] = 0;
}
add_q[k] += v;
}
}, this);
this._pop_from_people_queue(UNSET_ACTION, q_data);
} else if (q_key === UNION_QUEUE_KEY) {
_.each(q_data, function(v, k) {
if (_.isArray(v)) {
if (!(k in union_q)) {
union_q[k] = [];
}
// We may send duplicates, the server will dedup them.
union_q[k] = union_q[k].concat(v);
}
});
this._pop_from_people_queue(UNSET_ACTION, q_data);
} else if (q_key === APPEND_QUEUE_KEY) {
append_q.push(q_data);
this._pop_from_people_queue(UNSET_ACTION, q_data);
}
console$1.log('MIXPANEL PEOPLE REQUEST (QUEUED, PENDING IDENTIFY):');
console$1.log(data);
this.save();
};
MixpanelPersistence.prototype._pop_from_people_queue = function(queue, data) {
var q = this._get_queue(queue);
if (!_.isUndefined(q)) {
_.each(data, function(v, k) {
delete q[k];
}, this);
this.save();
}
};
MixpanelPersistence.prototype._get_queue_key = function(queue) {
if (queue === SET_ACTION) {
return SET_QUEUE_KEY;
} else if (queue === SET_ONCE_ACTION) {
return SET_ONCE_QUEUE_KEY;
} else if (queue === UNSET_ACTION) {
return UNSET_QUEUE_KEY;
} else if (queue === ADD_ACTION) {
return ADD_QUEUE_KEY;
} else if (queue === APPEND_ACTION) {
return APPEND_QUEUE_KEY;
} else if (queue === UNION_ACTION) {
return UNION_QUEUE_KEY;
} else {
console$1.error('Invalid queue:', queue);
}
};
MixpanelPersistence.prototype._get_queue = function(queue) {
return this['props'][this._get_queue_key(queue)];
};
MixpanelPersistence.prototype._get_or_create_queue = function(queue, default_val) {
var key = this._get_queue_key(queue);
default_val = _.isUndefined(default_val) ? {} : default_val;
return this['props'][key] || (this['props'][key] = default_val);
};
MixpanelPersistence.prototype.set_event_timer = function(event_name, timestamp) {
var timers = this['props'][EVENT_TIMERS_KEY] || {};
timers[event_name] = timestamp;
this['props'][EVENT_TIMERS_KEY] = timers;
this.save();
};
MixpanelPersistence.prototype.remove_event_timer = function(event_name) {
var timers = this['props'][EVENT_TIMERS_KEY] || {};
var timestamp = timers[event_name];
if (!_.isUndefined(timestamp)) {
delete this['props'][EVENT_TIMERS_KEY][event_name];
this.save();
}
return timestamp;
};
/**
* Mixpanel Library Object
* @constructor
*/
var MixpanelLib = function() {};
/**
* Mixpanel People Object
* @constructor
*/
var MixpanelPeople = function() {};
var MPNotif;
/**
* create_mplib(token:string, config:object, name:string)
*
* This function is used by the init method of MixpanelLib objects
* as well as the main initializer at the end of the JSLib (that
* initializes document.mixpanel as well as any additional instances
* declared before this file has loaded).
*/
var create_mplib = function(token, config, name) {
var instance,
target = (name === PRIMARY_INSTANCE_NAME) ? mixpanel_master : mixpanel_master[name];
if (target && init_type === INIT_MODULE) {
instance = target;
} else {
if (target && !_.isArray(target)) {
console$1.error('You have already initialized ' + name);
return;
}
instance = new MixpanelLib();
}
instance._init(token, config, name);
instance['people'] = new MixpanelPeople();
instance['people']._init(instance);
// if any instance on the page has debug = true, we set the
// global debug to be true
Config.DEBUG = Config.DEBUG || instance.get_config('debug');
instance['__autotrack_enabled'] = instance.get_config('autotrack');
if (instance.get_config('autotrack')) {
var num_buckets = 100;
var num_enabled_buckets = 100;
if (!autotrack.enabledForProject(instance.get_config('token'), num_buckets, num_enabled_buckets)) {
instance['__autotrack_enabled'] = false;
console$1.log('Not in active bucket: disabling Automatic Event Collection.');
} else if (!autotrack.isBrowserSupported()) {
instance['__autotrack_enabled'] = false;
console$1.log('Disabling Automatic Event Collection because this browser is not supported');
} else {
autotrack.init(instance);
}
}
// if target is not defined, we called init after the lib already
// loaded, so there won't be an array of things to execute
if (!_.isUndefined(target) && _.isArray(target)) {
// Crunch through the people queue first - we queue this data up &
// flush on identify, so it's better to do all these operations first
instance._execute_array.call(instance['people'], target['people']);
instance._execute_array(target);
}
return instance;
};
// Initialization methods
/**
* This function initializes a new instance of the Mixpanel tracking object.
* All new instances are added to the main mixpanel object as sub properties (such as
* mixpanel.library_name) and also returned by this function. To define a
* second instance on the page, you would call:
*
* mixpanel.init('new token', { your: 'config' }, 'library_name');
*
* and use it like so:
*
* mixpanel.library_name.track(...);
*
* @param {String} token Your Mixpanel API token
* @param {Object} [config] A dictionary of config options to override. <a href="https://github.com/mixpanel/mixpanel-js/blob/8b2e1f7b/src/mixpanel-core.js#L87-L110">See a list of default config options</a>.
* @param {String} [name] The name for the new mixpanel instance that you want created
*/
MixpanelLib.prototype.init = function (token, config, name) {
if (_.isUndefined(name)) {
console$1.error('You must name your new library: init(token, config, name)');
return;
}
if (name === PRIMARY_INSTANCE_NAME) {
console$1.error('You must initialize the main mixpanel object right after you include the Mixpanel js snippet');
return;
}
var instance = create_mplib(token, config, name);
mixpanel_master[name] = instance;
instance._loaded();
return instance;
};
// mixpanel._init(token:string, config:object, name:string)
//
// This function sets up the current instance of the mixpanel
// library. The difference between this method and the init(...)
// method is this one initializes the actual instance, whereas the
// init(...) method sets up a new library and calls _init on it.
//
MixpanelLib.prototype._init = function(token, config, name) {
this['__loaded'] = true;
this['config'] = {};
this.set_config(_.extend({}, DEFAULT_CONFIG, config, {
'name': name,
'token': token,
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
}));
this['_jsc'] = function() {};
this.__dom_loaded_queue = [];
this.__request_queue = [];
this.__disabled_events = [];
this._flags = {
'disable_all_events': false,
'identify_called': false
};
this['persistence'] = this['cookie'] = new MixpanelPersistence(this['config']);
this._init_gdpr_persistence();
this.register_once({'distinct_id': _.UUID()}, '');
};
// Private methods
MixpanelLib.prototype._update_persistence = function() {
var disablePersistence = this.get_config('disable_persistence') || this.has_opted_out_tracking();
if (this['persistence'].disabled !== disablePersistence) {
this['persistence'].set_disabled(disablePersistence);
}
};
MixpanelLib.prototype._loaded = function() {
this.get_config('loaded')(this);
// this happens after so a user can call identify/name_tag in
// the loaded callback
if (this.get_config('track_pageview')) {
this.track_pageview();
}
};
MixpanelLib.prototype._dom_loaded = function() {
_.each(this.__dom_loaded_queue, function(item) {
this._track_dom.apply(this, item);
}, this);
if (!this.has_opted_out_tracking()) {
_.each(this.__request_queue, function(item) {
this._send_request.apply(this, item);
}, this);
}
delete this.__dom_loaded_queue;
delete this.__request_queue;
};
MixpanelLib.prototype._track_dom = function(DomClass, args) {
if (this.get_config('img')) {
console$1.error('You can\'t use DOM tracking functions with img = true.');
return false;
}
if (!DOM_LOADED) {
this.__dom_loaded_queue.push([DomClass, args]);
return false;
}
var dt = new DomClass().init(this);
return dt.track.apply(dt, args);
};
/**
* _prepare_callback() should be called by callers of _send_request for use
* as the callback argument.
*
* If there is no callback, this returns null.
* If we are going to make XHR/XDR requests, this returns a function.
* If we are going to use script tags, this returns a string to use as the
* callback GET param.
*/
MixpanelLib.prototype._prepare_callback = function(callback, data) {
if (_.isUndefined(callback)) {
return null;
}
if (USE_XHR) {
var callback_function = function(response) {
callback(response, data);
};
return callback_function;
} else {
// if the user gives us a callback, we store as a random
// property on this instances jsc function and update our
// callback string to reflect that.
var jsc = this['_jsc'];
var randomized_cb = '' + Math.floor(Math.random() * 100000000);
var callback_string = this.get_config('callback_fn') + '[' + randomized_cb + ']';
jsc[randomized_cb] = function(response) {
delete jsc[randomized_cb];
callback(response, data);
};
return callback_string;
}
};
MixpanelLib.prototype._send_request = function(url, data, callback) {
if (ENQUEUE_REQUESTS) {
this.__request_queue.push(arguments);
return;
}
// needed to correctly format responses
var verbose_mode = this.get_config('verbose');
if (data['verbose']) { verbose_mode = true; }
if (this.get_config('test')) { data['test'] = 1; }
if (verbose_mode) { data['verbose'] = 1; }
if (this.get_config('img')) { data['img'] = 1; }
if (!USE_XHR) {
if (callback) {
data['callback'] = callback;
} else if (verbose_mode || this.get_config('test')) {
// Verbose output (from verbose mode, or an error in test mode) is a json blob,
// which by itself is not valid javascript. Without a callback, this verbose output will
// cause an error when returned via jsonp, so we force a no-op callback param.
// See the ECMA script spec: http://www.ecma-international.org/ecma-262/5.1/#sec-12.4
data['callback'] = '(function(){})';
}
}
data['ip'] = this.get_config('ip')?1:0;
data['_'] = new Date().getTime().toString();
url += '?' + _.HTTPBuildQuery(data);
if ('img' in data) {
var img = document$1.createElement('img');
img.src = url;
document$1.body.appendChild(img);
} else if (USE_XHR) {
try {
var req = new XMLHttpRequest();
req.open('GET', url, true);
var headers = this.get_config('xhr_headers');
_.each(headers, function(headerValue, headerName) {
req.setRequestHeader(headerName, headerValue);
});
// send the mp_optout cookie
// withCredentials cannot be modified until after calling .open on Android and Mobile Safari
req.withCredentials = true;
req.onreadystatechange = function () {
if (req.readyState === 4) { // XMLHttpRequest.DONE == 4, except in safari 4
if (req.status === 200) {
if (callback) {
if (verbose_mode) {
var response;
try {
response = _.JSONDecode(req.responseText);
} catch (e) {
console$1.error(e);
return;
}
callback(response);
} else {
callback(Number(req.responseText));
}
}
} else {
var error = 'Bad HTTP status: ' + req.status + ' ' + req.statusText;
console$1.error(error);
if (callback) {
if (verbose_mode) {
callback({status: 0, error: error});
} else {
callback(0);
}
}
}
}
};
req.send(null);
} catch (e) {
console$1.error(e);
}
} else {
var script = document$1.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.defer = true;
script.src = url;
var s = document$1.getElementsByTagName('script')[0];
s.parentNode.insertBefore(script, s);
}
};
/**
* _execute_array() deals with processing any mixpanel function
* calls that were called before the Mixpanel library were loaded
* (and are thus stored in an array so they can be called later)
*
* Note: we fire off all the mixpanel function calls && user defined
* functions BEFORE we fire off mixpanel tracking calls. This is so
* identify/register/set_config calls can properly modify early
* tracking calls.
*
* @param {Array} array
*/
MixpanelLib.prototype._execute_array = function(array) {
var fn_name, alias_calls = [], other_calls = [], tracking_calls = [];
_.each(array, function(item) {
if (item) {
fn_name = item[0];
if (typeof(item) === 'function') {
item.call(this);
} else if (_.isArray(item) && fn_name === 'alias') {
alias_calls.push(item);
} else if (_.isArray(item) && fn_name.indexOf('track') !== -1 && typeof(this[fn_name]) === 'function') {
tracking_calls.push(item);
} else {
other_calls.push(item);
}
}
}, this);
var execute = function(calls, context) {
_.each(calls, function(item) {
this[item[0]].apply(this, item.slice(1));
}, context);
};
execute(alias_calls, this);
execute(other_calls, this);
execute(tracking_calls, this);
};
/**
* push() keeps the standard async-array-push
* behavior around after the lib is loaded.
* This is only useful for external integrations that
* do not wish to rely on our convenience methods
* (created in the snippet).
*
* ### Usage:
* mixpanel.push(['register', { a: 'b' }]);
*
* @param {Array} item A [function_name, args...] array to be executed
*/
MixpanelLib.prototype.push = function(item) {
this._execute_array([item]);
};
/**
* Disable events on the Mixpanel object. If passed no arguments,
* this function disables tracking of any event. If passed an
* array of event names, those events will be disabled, but other
* events will continue to be tracked.
*
* Note: this function does not stop other mixpanel functions from
* firing, such as register() or people.set().
*
* @param {Array} [events] An array of event names to disable
*/
MixpanelLib.prototype.disable = function(events) {
if (typeof(events) === 'undefined') {
this._flags.disable_all_events = true;
} else {
this.__disabled_events = this.__disabled_events.concat(events);
}
};
/**
* Track an event. This is the most important and
* frequently used Mixpanel function.
*
* ### Usage:
*
* // track an event named 'Registered'
* mixpanel.track('Registered', {'Gender': 'Male', 'Age': 21});
*
* To track link clicks or form submissions, see track_links() or track_forms().
*
* @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc.
* @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself.
* @param {Function} [callback] If provided, the callback function will be called after tracking the event.
*/
MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, properties, callback) {
if (typeof(callback) !== 'function') {
callback = function() {};
}
if (_.isUndefined(event_name)) {
console$1.error('No event name provided to mixpanel.track');
return;
}
if (this._event_is_disabled(event_name)) {
callback(0);
return;
}
// set defaults
properties = properties || {};
properties['token'] = this.get_config('token');
// set $duration if time_event was previously called for this event
var start_timestamp = this['persistence'].remove_event_timer(event_name);
if (!_.isUndefined(start_timestamp)) {
var duration_in_ms = new Date().getTime() - start_timestamp;
properties['$duration'] = parseFloat((duration_in_ms / 1000).toFixed(3));
}
// update persistence
this['persistence'].update_search_keyword(document$1.referrer);
if (this.get_config('store_google')) { this['persistence'].update_campaign_params(); }
if (this.get_config('save_referrer')) { this['persistence'].update_referrer_info(document$1.referrer); }
// note: extend writes to the first object, so lets make sure we
// don't write to the persistence properties object and info
// properties object by passing in a new object
// update properties with pageview info and super-properties
properties = _.extend(
{},
_.info.properties(),
this['persistence'].properties(),
properties
);
var property_blacklist = this.get_config('property_blacklist');
if (_.isArray(property_blacklist)) {
_.each(property_blacklist, function(blacklisted_prop) {
delete properties[blacklisted_prop];
});
} else {
console$1.error('Invalid value for property_blacklist config: ' + property_blacklist);
}
var data = {
'event': event_name,
'properties': properties
};
var truncated_data = _.truncate(data, 255);
var json_data = _.JSONEncode(truncated_data);
var encoded_data = _.base64Encode(json_data);
console$1.log('MIXPANEL REQUEST:');
console$1.log(truncated_data);
this._send_request(
this.get_config('api_host') + '/track/',
{ 'data': encoded_data },
this._prepare_callback(callback, truncated_data)
);
return truncated_data;
});
/**
* Track a page view event, which is currently ignored by the server.
* This function is called by default on page load unless the
* track_pageview configuration variable is false.
*
* @param {String} [page] The url of the page to record. If you don't include this, it defaults to the current url.
* @api private
*/
MixpanelLib.prototype.track_pageview = function(page) {
if (_.isUndefined(page)) {
page = document$1.location.href;
}
this.track('mp_page_view', _.info.pageviewInfo(page));
};
/**
* Track clicks on a set of document elements. Selector must be a
* valid query. Elements must exist on the page at the time track_links is called.
*
* ### Usage:
*
* // track click for link id #nav
* mixpanel.track_links('#nav', 'Clicked Nav Link');
*
* ### Notes:
*
* This function will wait up to 300 ms for the Mixpanel
* servers to respond. If they have not responded by that time
* it will head to the link without ensuring that your event
* has been tracked. To configure this timeout please see the
* set_config() documentation below.
*
* If you pass a function in as the properties argument, the
* function will receive the DOMElement that triggered the
* event as an argument. You are expected to return an object
* from the function; any properties defined on this object
* will be sent to mixpanel as event properties.
*
* @type {Function}
* @param {Object|String} query A valid DOM query, element or jQuery-esque list
* @param {String} event_name The name of the event to track
* @param {Object|Function} [properties] A properties object or function that returns a dictionary of properties when passed a DOMElement
*/
MixpanelLib.prototype.track_links = function() {
return this._track_dom.call(this, LinkTracker, arguments);
};
/**
* Track form submissions. Selector must be a valid query.
*
* ### Usage:
*
* // track submission for form id 'register'
* mixpanel.track_forms('#register', 'Created Account');
*
* ### Notes:
*
* This function will wait up to 300 ms for the mixpanel
* servers to respond, if they have not responded by that time
* it will head to the link without ensuring that your event
* has been tracked. To configure this timeout please see the
* set_config() documentation below.
*
* If you pass a function in as the properties argument, the
* function will receive the DOMElement that triggered the
* event as an argument. You are expected to return an object
* from the function; any properties defined on this object
* will be sent to mixpanel as event properties.
*
* @type {Function}
* @param {Object|String} query A valid DOM query, element or jQuery-esque list
* @param {String} event_name The name of the event to track
* @param {Object|Function} [properties] This can be a set of properties, or a function that returns a set of properties after being passed a DOMElement
*/
MixpanelLib.prototype.track_forms = function() {
return this._track_dom.call(this, FormTracker, arguments);
};
/**
* Time an event by including the time between this call and a
* later 'track' call for the same event in the properties sent
* with the event.
*
* ### Usage:
*
* // time an event named 'Registered'
* mixpanel.time_event('Registered');
* mixpanel.track('Registered', {'Gender': 'Male', 'Age': 21});
*
* When called for a particular event name, the next track call for that event
* name will include the elapsed time between the 'time_event' and 'track'
* calls. This value is stored as seconds in the '$duration' property.
*
* @param {String} event_name The name of the event.
*/
MixpanelLib.prototype.time_event = function(event_name) {
if (_.isUndefined(event_name)) {
console$1.error('No event name provided to mixpanel.time_event');
return;
}
if (this._event_is_disabled(event_name)) {
return;
}
this['persistence'].set_event_timer(event_name, new Date().getTime());
};
/**
* Register a set of super properties, which are included with all
* events. This will overwrite previous super property values.
*
* ### Usage:
*
* // register 'Gender' as a super property
* mixpanel.register({'Gender': 'Female'});
*
* // register several super properties when a user signs up
* mixpanel.register({
* 'Email': '[email protected]',
* 'Account Type': 'Free'
* });
*
* @param {Object} properties An associative array of properties to store about the user
* @param {Number} [days] How many days since the user's last visit to store the super properties
*/
MixpanelLib.prototype.register = function(props, days) {
this['persistence'].register(props, days);
};
/**
* Register a set of super properties only once. This will not
* overwrite previous super property values, unlike register().
*
* ### Usage:
*
* // register a super property for the first time only
* mixpanel.register_once({
* 'First Login Date': new Date().toISOString()
* });
*
* ### Notes:
*
* If default_value is specified, current super properties
* with that value will be overwritten.
*
* @param {Object} properties An associative array of properties to store about the user
* @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None'
* @param {Number} [days] How many days since the users last visit to store the super properties
*/
MixpanelLib.prototype.register_once = function(props, default_value, days) {
this['persistence'].register_once(props, default_value, days);
};
/**
* Delete a super property stored with the current user.
*
* @param {String} property The name of the super property to remove
*/
MixpanelLib.prototype.unregister = function(property) {
this['persistence'].unregister(property);
};
MixpanelLib.prototype._register_single = function(prop, value) {
var props = {};
props[prop] = value;
this.register(props);
};
/**
* Identify a user with a unique ID instead of a Mixpanel
* randomly generated distinct_id. If the method is never called,
* then unique visitors will be identified by a UUID generated
* the first time they visit the site.
*
* ### Notes:
*
* You can call this function to overwrite a previously set
* unique ID for the current user. Mixpanel cannot translate
* between IDs at this time, so when you change a user's ID
* they will appear to be a new user.
*
* When used alone, mixpanel.identify will change the user's
* distinct_id to the unique ID provided. When used in tandem
* with mixpanel.alias, it will allow you to identify based on
* unique ID and map that back to the original, anonymous
* distinct_id given to the user upon her first arrival to your
* site (thus connecting anonymous pre-signup activity to
* post-signup activity). Though the two work together, do not
* call identify() at the same time as alias(). Calling the two
* at the same time can cause a race condition, so it is best
* practice to call identify on the original, anonymous ID
* right after you've aliased it.
* <a href="https://mixpanel.com/help/questions/articles/how-should-i-handle-my-user-identity-with-the-mixpanel-javascript-library">Learn more about how mixpanel.identify and mixpanel.alias can be used</a>.
*
* @param {String} [unique_id] A string that uniquely identifies a user. If not provided, the distinct_id currently in the persistent store (cookie or localStorage) will be used.
*/
MixpanelLib.prototype.identify = function(
unique_id, _set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback
) {
// Optional Parameters
// _set_callback:function A callback to be run if and when the People set queue is flushed
// _add_callback:function A callback to be run if and when the People add queue is flushed
// _append_callback:function A callback to be run if and when the People append queue is flushed
// _set_once_callback:function A callback to be run if and when the People set_once queue is flushed
// _union_callback:function A callback to be run if and when the People union queue is flushed
// _unset_callback:function A callback to be run if and when the People unset queue is flushed
// identify only changes the distinct id if it doesn't match either the existing or the alias;
// if it's new, blow away the alias as well.
if (unique_id !== this.get_distinct_id() && unique_id !== this.get_property(ALIAS_ID_KEY)) {
this.unregister(ALIAS_ID_KEY);
this._register_single('distinct_id', unique_id);
}
this._check_and_handle_notifications(this.get_distinct_id());
this._flags.identify_called = true;
// Flush any queued up people requests
this['people']._flush(_set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback);
};
/**
* Clears super properties and generates a new random distinct_id for this instance.
* Useful for clearing data when a user logs out.
*/
MixpanelLib.prototype.reset = function() {
this['persistence'].clear();
this._flags.identify_called = false;
this.register_once({'distinct_id': _.UUID()}, '');
};
/**
* Returns the current distinct id of the user. This is either the id automatically
* generated by the library or the id that has been passed by a call to identify().
*
* ### Notes:
*
* get_distinct_id() can only be called after the Mixpanel library has finished loading.
* init() has a loaded function available to handle this automatically. For example:
*
* // set distinct_id after the mixpanel library has loaded
* mixpanel.init('YOUR PROJECT TOKEN', {
* loaded: function(mixpanel) {
* distinct_id = mixpanel.get_distinct_id();
* }
* });
*/
MixpanelLib.prototype.get_distinct_id = function() {
return this.get_property('distinct_id');
};
/**
* Create an alias, which Mixpanel will use to link two distinct_ids going forward (not retroactively).
* Multiple aliases can map to the same original ID, but not vice-versa. Aliases can also be chained - the
* following is a valid scenario:
*
* mixpanel.alias('new_id', 'existing_id');
* ...
* mixpanel.alias('newer_id', 'new_id');
*
* If the original ID is not passed in, we will use the current distinct_id - probably the auto-generated GUID.
*
* ### Notes:
*
* The best practice is to call alias() when a unique ID is first created for a user
* (e.g., when a user first registers for an account and provides an email address).
* alias() should never be called more than once for a given user, except to
* chain a newer ID to a previously new ID, as described above.
*
* @param {String} alias A unique identifier that you want to use for this user in the future.
* @param {String} [original] The current identifier being used for this user.
*/
MixpanelLib.prototype.alias = function(alias, original) {
// If the $people_distinct_id key exists in persistence, there has been a previous
// mixpanel.people.identify() call made for this user. It is VERY BAD to make an alias with
// this ID, as it will duplicate users.
if (alias === this.get_property(PEOPLE_DISTINCT_ID_KEY)) {
console$1.critical('Attempting to create alias for existing People user - aborting.');
return -2;
}
var _this = this;
if (_.isUndefined(original)) {
original = this.get_distinct_id();
}
if (alias !== original) {
this._register_single(ALIAS_ID_KEY, alias);
return this.track('$create_alias', { 'alias': alias, 'distinct_id': original }, function() {
// Flush the people queue
_this.identify(alias);
});
} else {
console$1.error('alias matches current distinct_id - skipping api call.');
this.identify(alias);
return -1;
}
};
/**
* Provide a string to recognize the user by. The string passed to
* this method will appear in the Mixpanel Streams product rather
* than an automatically generated name. Name tags do not have to
* be unique.
*
* This value will only be included in Streams data.
*
* @param {String} name_tag A human readable name for the user
* @api private
*/
MixpanelLib.prototype.name_tag = function(name_tag) {
this._register_single('mp_name_tag', name_tag);
};
/**
* Update the configuration of a mixpanel library instance.
*
* The default config is:
*
* {
* // super properties cookie expiration (in days)
* cookie_expiration: 365
*
* // super properties span subdomains
* cross_subdomain_cookie: true
*
* // debug mode
* debug: false
*
* // if this is true, the mixpanel cookie or localStorage entry
* // will be deleted, and no user persistence will take place
* disable_persistence: false
*
* // if this is true, Mixpanel will automatically determine
* // City, Region and Country data using the IP address of
* //the client
* ip: true
*
* // opt users out of tracking by this Mixpanel instance by default
* opt_out_tracking_by_default: false
*
* // persistence mechanism used by opt-in/opt-out methods - cookie
* // or localStorage - falls back to cookie if localStorage is unavailable
* opt_out_tracking_persistence_type: 'localStorage'
*
* // customize the name of cookie/localStorage set by opt-in/opt-out methods
* opt_out_tracking_cookie_prefix: null
*
* // type of persistent store for super properties (cookie/
* // localStorage) if set to 'localStorage', any existing
* // mixpanel cookie value with the same persistence_name
* // will be transferred to localStorage and deleted
* persistence: 'cookie'
*
* // name for super properties persistent store
* persistence_name: ''
*
* // names of properties/superproperties which should never
* // be sent with track() calls
* property_blacklist: []
*
* // if this is true, mixpanel cookies will be marked as
* // secure, meaning they will only be transmitted over https
* secure_cookie: false
*
* // the amount of time track_links will
* // wait for Mixpanel's servers to respond
* track_links_timeout: 300
*
* // should we track a page view on page load
* track_pageview: true
*
* // if you set upgrade to be true, the library will check for
* // a cookie from our old js library and import super
* // properties from it, then the old cookie is deleted
* // The upgrade config option only works in the initialization,
* // so make sure you set it when you create the library.
* upgrade: false
*
* // extra HTTP request headers to set for each API request, in
* // the format {'Header-Name': value}
* xhr_headers: {}
* }
*
*
* @param {Object} config A dictionary of new configuration values to update
*/
MixpanelLib.prototype.set_config = function(config) {
if (_.isObject(config)) {
_.extend(this['config'], config);
if (!this.get_config('persistence_name')) {
this['config']['persistence_name'] = this['config']['cookie_name'];
}
if (!this.get_config('disable_persistence')) {
this['config']['disable_persistence'] = this['config']['disable_cookie'];
}
if (this['persistence']) {
this['persistence'].update_config(this['config']);
}
Config.DEBUG = Config.DEBUG || this.get_config('debug');
}
};
/**
* returns the current config object for the library.
*/
MixpanelLib.prototype.get_config = function(prop_name) {
return this['config'][prop_name];
};
/**
* Returns the value of the super property named property_name. If no such
* property is set, get_property() will return the undefined value.
*
* ### Notes:
*
* get_property() can only be called after the Mixpanel library has finished loading.
* init() has a loaded function available to handle this automatically. For example:
*
* // grab value for 'user_id' after the mixpanel library has loaded
* mixpanel.init('YOUR PROJECT TOKEN', {
* loaded: function(mixpanel) {
* user_id = mixpanel.get_property('user_id');
* }
* });
*
* @param {String} property_name The name of the super property you want to retrieve
*/
MixpanelLib.prototype.get_property = function(property_name) {
return this['persistence']['props'][property_name];
};
MixpanelLib.prototype.toString = function() {
var name = this.get_config('name');
if (name !== PRIMARY_INSTANCE_NAME) {
name = PRIMARY_INSTANCE_NAME + '.' + name;
}
return name;
};
MixpanelLib.prototype._event_is_disabled = function(event_name) {
return _.isBlockedUA(userAgent) ||
this._flags.disable_all_events ||
_.include(this.__disabled_events, event_name);
};
MixpanelLib.prototype._check_and_handle_notifications = addOptOutCheckMixpanelLib(function(distinct_id) {
if (
!distinct_id ||
this._flags.identify_called ||
this.get_config('disable_notifications')
) {
return;
}
console$1.log('MIXPANEL NOTIFICATION CHECK');
var data = {
'verbose': true,
'version': '2',
'lib': 'web',
'token': this.get_config('token'),
'distinct_id': distinct_id
};
var self = this;
this._send_request(
this.get_config('api_host') + '/decide/',
data,
this._prepare_callback(function(r) {
if (r['notifications'] && r['notifications'].length > 0) {
self._show_notification.call(self, r['notifications'][0]);
}
})
);
});
MixpanelLib.prototype._show_notification = function(notification_data) {
var notification = new MPNotif(notification_data, this);
notification.show();
};
// perform some housekeeping around GDPR persistence of opt-in/out state
MixpanelLib.prototype._init_gdpr_persistence = function() {
var is_localStorage_requested = this.get_config('opt_out_tracking_persistence_type') === 'localStorage';
// try to convert opt-in/out cookies to localStorage if possible
if (is_localStorage_requested && _.localStorage.is_supported()) {
if (!this.has_opted_in_tracking() && this.has_opted_in_tracking({'persistence_type': 'cookie'})) {
this.opt_in_tracking();
}
if (!this.has_opted_out_tracking() && this.has_opted_out_tracking({'persistence_type': 'cookie'})) {
this.opt_out_tracking();
}
this.clear_opt_in_out_tracking({'persistence_type': 'cookie'});
}
// check whether we should opt out by default and update persistence accordingly
if (this.get_config('opt_out_tracking_by_default') || _.cookie.get('mp_optout')) {
_.cookie.remove('mp_optout');
this.opt_out_tracking();
}
this._update_persistence();
};
// call a base gdpr function after constructing the appropriate token and options args
MixpanelLib.prototype._call_gdpr_func = function(func, options) {
options = _.extend({
'track': _.bind(this.track, this),
'persistence_type': this.get_config('opt_out_tracking_persistence_type'),
'cookie_prefix': this.get_config('opt_out_tracking_cookie_prefix'),
'cookie_expiration': this.get_config('cookie_expiration'),
'cross_subdomain_cookie': this.get_config('cross_subdomain_cookie'),
'secure_cookie': this.get_config('secure_cookie')
}, options);
// check if localStorage can be used for recording opt out status, fall back to cookie if not
if (!_.localStorage.is_supported()) {
options['persistence_type'] = 'cookie';
}
return func(this.get_config('token'), {
track: options['track'],
trackEventName: options['track_event_name'],
trackProperties: options['track_properties'],
persistenceType: options['persistence_type'],
persistencePrefix: options['cookie_prefix'],
cookieExpiration: options['cookie_expiration'],
crossSubdomainCookie: options['cross_subdomain_cookie'],
secureCookie: options['secure_cookie']
});
};
/**
* Opt the user in to data tracking and cookies/localstorage for this Mixpanel instance
*
* ### Usage
*
* // opt user in
* mixpanel.opt_in_tracking();
*
* // opt user in with specific event name, properties, cookie configuration
* mixpanel.opt_in_tracking({
* track_event_name: 'User opted in',
* track_event_properties: {
* 'Email': '[email protected]'
* },
* cookie_expiration: 30,
* secure_cookie: true
* });
*
* @param {Object} [options] A dictionary of config options to override
* @param {function} [options.track] Function used for tracking a Mixpanel event to record the opt-in action (default is this Mixpanel instance's track method)
* @param {string} [options.track_event_name=$opt_in] Event name to be used for tracking the opt-in action
* @param {Object} [options.track_properties] Set of properties to be tracked along with the opt-in action
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable
* @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name
* @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config)
* @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config)
* @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config)
*/
MixpanelLib.prototype.opt_in_tracking = function(options) {
this._call_gdpr_func(optIn, options);
this._update_persistence();
};
/**
* Opt the user out of data tracking and cookies/localstorage for this Mixpanel instance
*
* ### Usage
*
* // opt user out
* mixpanel.opt_out_tracking();
*
* // opt user out with different cookie configuration from Mixpanel instance
* mixpanel.opt_out_tracking({
* cookie_expiration: 30,
* secure_cookie: true
* });
*
* @param {Object} [options] A dictionary of config options to override
* @param {boolean} [options.delete_user=true] If true, will delete the currently identified user's profile and clear all charges after opting the user out
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable
* @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name
* @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config)
* @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config)
* @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config)
*/
MixpanelLib.prototype.opt_out_tracking = function(options) {
// delete use and clear charges since these methods may be disabled by opt-out
options = _.extend({'delete_user': true}, options);
if (options['delete_user'] && this['people'] && this['people']._identify_called()) {
this['people'].delete_user();
this['people'].clear_charges();
}
this._call_gdpr_func(optOut, options);
this._update_persistence();
};
/**
* Check whether the user has opted in to data tracking and cookies/localstorage for this Mixpanel instance
*
* ### Usage
*
* var has_opted_in = mixpanel.has_opted_in_tracking();
* // use has_opted_in value
*
* @param {Object} [options] A dictionary of config options to override
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable
* @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name
* @returns {boolean} current opt-in status
*/
MixpanelLib.prototype.has_opted_in_tracking = function(options) {
return this._call_gdpr_func(hasOptedIn, options);
};
/**
* Check whether the user has opted out of data tracking and cookies/localstorage for this Mixpanel instance
*
* ### Usage
*
* var has_opted_out = mixpanel.has_opted_out_tracking();
* // use has_opted_out value
*
* @param {Object} [options] A dictionary of config options to override
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable
* @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name
* @returns {boolean} current opt-out status
*/
MixpanelLib.prototype.has_opted_out_tracking = function(options) {
return this._call_gdpr_func(hasOptedOut, options);
};
/**
* Clear the user's opt in/out status of data tracking and cookies/localstorage for this Mixpanel instance
*
* ### Usage
*
* // clear user's opt-in/out status
* mixpanel.clear_opt_in_out_tracking();
*
* // clear user's opt-in/out status with specific cookie configuration - should match
* // configuration used when opt_in_tracking/opt_out_tracking methods were called.
* mixpanel.clear_opt_in_out_tracking({
* cookie_expiration: 30,
* secure_cookie: true
* });
*
* @param {Object} [options] A dictionary of config options to override
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable
* @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name
* @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config)
* @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config)
* @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config)
*/
MixpanelLib.prototype.clear_opt_in_out_tracking = function(options) {
this._call_gdpr_func(clearOptInOut, options);
this._update_persistence();
};
MixpanelPeople.prototype._init = function(mixpanel_instance) {
this._mixpanel = mixpanel_instance;
};
/*
* Set properties on a user record.
*
* ### Usage:
*
* mixpanel.people.set('gender', 'm');
*
* // or set multiple properties at once
* mixpanel.people.set({
* 'Company': 'Acme',
* 'Plan': 'Premium',
* 'Upgrade date': new Date()
* });
* // properties can be strings, integers, dates, or lists
*
* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values.
* @param {*} [to] A value to set on the given property name
* @param {Function} [callback] If provided, the callback will be called after the tracking event
*/
MixpanelPeople.prototype.set = addOptOutCheckMixpanelPeople(function(prop, to, callback) {
var data = {};
var $set = {};
if (_.isObject(prop)) {
_.each(prop, function(v, k) {
if (!this._is_reserved_property(k)) {
$set[k] = v;
}
}, this);
callback = to;
} else {
$set[prop] = to;
}
// make sure that the referrer info has been updated and saved
if (this._get_config('save_referrer')) {
this._mixpanel['persistence'].update_referrer_info(document$1.referrer);
}
// update $set object with default people properties
$set = _.extend(
{},
_.info.people_properties(),
this._mixpanel['persistence'].get_referrer_info(),
$set
);
data[SET_ACTION] = $set;
return this._send_request(data, callback);
});
/*
* Set properties on a user record, only if they do not yet exist.
* This will not overwrite previous people property values, unlike
* people.set().
*
* ### Usage:
*
* mixpanel.people.set_once('First Login Date', new Date());
*
* // or set multiple properties at once
* mixpanel.people.set_once({
* 'First Login Date': new Date(),
* 'Starting Plan': 'Premium'
* });
*
* // properties can be strings, integers or dates
*
* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values.
* @param {*} [to] A value to set on the given property name
* @param {Function} [callback] If provided, the callback will be called after the tracking event
*/
MixpanelPeople.prototype.set_once = addOptOutCheckMixpanelPeople(function(prop, to, callback) {
var data = {};
var $set_once = {};
if (_.isObject(prop)) {
_.each(prop, function(v, k) {
if (!this._is_reserved_property(k)) {
$set_once[k] = v;
}
}, this);
callback = to;
} else {
$set_once[prop] = to;
}
data[SET_ONCE_ACTION] = $set_once;
return this._send_request(data, callback);
});
/*
* Unset properties on a user record (permanently removes the properties and their values from a profile).
*
* ### Usage:
*
* mixpanel.people.unset('gender');
*
* // or unset multiple properties at once
* mixpanel.people.unset(['gender', 'Company']);
*
* @param {Array|String} prop If a string, this is the name of the property. If an array, this is a list of property names.
* @param {Function} [callback] If provided, the callback will be called after the tracking event
*/
MixpanelPeople.prototype.unset = function(prop, callback) {
var data = {};
var $unset = [];
if (!_.isArray(prop)) {
prop = [prop];
}
_.each(prop, function(k) {
if (!this._is_reserved_property(k)) {
$unset.push(k);
}
}, this);
data[UNSET_ACTION] = $unset;
return this._send_request(data, callback);
};
/*
* Increment/decrement numeric people analytics properties.
*
* ### Usage:
*
* mixpanel.people.increment('page_views', 1);
*
* // or, for convenience, if you're just incrementing a counter by
* // 1, you can simply do
* mixpanel.people.increment('page_views');
*
* // to decrement a counter, pass a negative number
* mixpanel.people.increment('credits_left', -1);
*
* // like mixpanel.people.set(), you can increment multiple
* // properties at once:
* mixpanel.people.increment({
* counter1: 1,
* counter2: 6
* });
*
* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and numeric values.
* @param {Number} [by] An amount to increment the given property
* @param {Function} [callback] If provided, the callback will be called after the tracking event
*/
MixpanelPeople.prototype.increment = addOptOutCheckMixpanelPeople(function(prop, by, callback) {
var data = {};
var $add = {};
if (_.isObject(prop)) {
_.each(prop, function(v, k) {
if (!this._is_reserved_property(k)) {
if (isNaN(parseFloat(v))) {
console$1.error('Invalid increment value passed to mixpanel.people.increment - must be a number');
return;
} else {
$add[k] = v;
}
}
}, this);
callback = by;
} else {
// convenience: mixpanel.people.increment('property'); will
// increment 'property' by 1
if (_.isUndefined(by)) {
by = 1;
}
$add[prop] = by;
}
data[ADD_ACTION] = $add;
return this._send_request(data, callback);
});
/*
* Append a value to a list-valued people analytics property.
*
* ### Usage:
*
* // append a value to a list, creating it if needed
* mixpanel.people.append('pages_visited', 'homepage');
*
* // like mixpanel.people.set(), you can append multiple
* // properties at once:
* mixpanel.people.append({
* list1: 'bob',
* list2: 123
* });
*
* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values.
* @param {*} [value] An item to append to the list
* @param {Function} [callback] If provided, the callback will be called after the tracking event
*/
MixpanelPeople.prototype.append = addOptOutCheckMixpanelPeople(function(list_name, value, callback) {
var data = {};
var $append = {};
if (_.isObject(list_name)) {
_.each(list_name, function(v, k) {
if (!this._is_reserved_property(k)) {
$append[k] = v;
}
}, this);
callback = value;
} else {
$append[list_name] = value;
}
data[APPEND_ACTION] = $append;
return this._send_request(data, callback);
});
/*
* Merge a given list with a list-valued people analytics property,
* excluding duplicate values.
*
* ### Usage:
*
* // merge a value to a list, creating it if needed
* mixpanel.people.union('pages_visited', 'homepage');
*
* // like mixpanel.people.set(), you can append multiple
* // properties at once:
* mixpanel.people.union({
* list1: 'bob',
* list2: 123
* });
*
* // like mixpanel.people.append(), you can append multiple
* // values to the same list:
* mixpanel.people.union({
* list1: ['bob', 'billy']
* });
*
* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values.
* @param {*} [value] Value / values to merge with the given property
* @param {Function} [callback] If provided, the callback will be called after the tracking event
*/
MixpanelPeople.prototype.union = addOptOutCheckMixpanelPeople(function(list_name, values, callback) {
var data = {};
var $union = {};
if (_.isObject(list_name)) {
_.each(list_name, function(v, k) {
if (!this._is_reserved_property(k)) {
$union[k] = _.isArray(v) ? v : [v];
}
}, this);
callback = values;
} else {
$union[list_name] = _.isArray(values) ? values : [values];
}
data[UNION_ACTION] = $union;
return this._send_request(data, callback);
});
/*
* Record that you have charged the current user a certain amount
* of money. Charges recorded with track_charge() will appear in the
* Mixpanel revenue report.
*
* ### Usage:
*
* // charge a user $50
* mixpanel.people.track_charge(50);
*
* // charge a user $30.50 on the 2nd of january
* mixpanel.people.track_charge(30.50, {
* '$time': new Date('jan 1 2012')
* });
*
* @param {Number} amount The amount of money charged to the current user
* @param {Object} [properties] An associative array of properties associated with the charge
* @param {Function} [callback] If provided, the callback will be called when the server responds
*/
MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function(amount, properties, callback) {
if (!_.isNumber(amount)) {
amount = parseFloat(amount);
if (isNaN(amount)) {
console$1.error('Invalid value passed to mixpanel.people.track_charge - must be a number');
return;
}
}
return this.append('$transactions', _.extend({
'$amount': amount
}, properties), callback);
});
/*
* Permanently clear all revenue report transactions from the
* current user's people analytics profile.
*
* ### Usage:
*
* mixpanel.people.clear_charges();
*
* @param {Function} [callback] If provided, the callback will be called after the tracking event
*/
MixpanelPeople.prototype.clear_charges = function(callback) {
return this.set('$transactions', [], callback);
};
/*
* Permanently deletes the current people analytics profile from
* Mixpanel (using the current distinct_id).
*
* ### Usage:
*
* // remove the all data you have stored about the current user
* mixpanel.people.delete_user();
*
*/
MixpanelPeople.prototype.delete_user = function() {
if (!this._identify_called()) {
console$1.error('mixpanel.people.delete_user() requires you to call identify() first');
return;
}
var data = {'$delete': this._mixpanel.get_distinct_id()};
return this._send_request(data);
};
MixpanelPeople.prototype.toString = function() {
return this._mixpanel.toString() + '.people';
};
MixpanelPeople.prototype._send_request = function(data, callback) {
data['$token'] = this._get_config('token');
data['$distinct_id'] = this._mixpanel.get_distinct_id();
var date_encoded_data = _.encodeDates(data);
var truncated_data = _.truncate(date_encoded_data, 255);
var json_data = _.JSONEncode(date_encoded_data);
var encoded_data = _.base64Encode(json_data);
if (!this._identify_called()) {
this._enqueue(data);
if (!_.isUndefined(callback)) {
if (this._get_config('verbose')) {
callback({status: -1, error: null});
} else {
callback(-1);
}
}
return truncated_data;
}
console$1.log('MIXPANEL PEOPLE REQUEST:');
console$1.log(truncated_data);
this._mixpanel._send_request(
this._get_config('api_host') + '/engage/',
{'data': encoded_data},
this._mixpanel._prepare_callback(callback, truncated_data)
);
return truncated_data;
};
MixpanelPeople.prototype._get_config = function(conf_var) {
return this._mixpanel.get_config(conf_var);
};
MixpanelPeople.prototype._identify_called = function() {
return this._mixpanel._flags.identify_called === true;
};
// Queue up engage operations if identify hasn't been called yet.
MixpanelPeople.prototype._enqueue = function(data) {
if (SET_ACTION in data) {
this._mixpanel['persistence']._add_to_people_queue(SET_ACTION, data);
} else if (SET_ONCE_ACTION in data) {
this._mixpanel['persistence']._add_to_people_queue(SET_ONCE_ACTION, data);
} else if (UNSET_ACTION in data) {
this._mixpanel['persistence']._add_to_people_queue(UNSET_ACTION, data);
} else if (ADD_ACTION in data) {
this._mixpanel['persistence']._add_to_people_queue(ADD_ACTION, data);
} else if (APPEND_ACTION in data) {
this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, data);
} else if (UNION_ACTION in data) {
this._mixpanel['persistence']._add_to_people_queue(UNION_ACTION, data);
} else {
console$1.error('Invalid call to _enqueue():', data);
}
};
MixpanelPeople.prototype._flush_one_queue = function(action, action_method, callback, queue_to_params_fn) {
var _this = this;
var queued_data = _.extend({}, this._mixpanel['persistence']._get_queue(action));
var action_params = queued_data;
if (!_.isUndefined(queued_data) && _.isObject(queued_data) && !_.isEmptyObject(queued_data)) {
_this._mixpanel['persistence']._pop_from_people_queue(action, queued_data);
if (queue_to_params_fn) {
action_params = queue_to_params_fn(queued_data);
}
action_method.call(_this, action_params, function(response, data) {
// on bad response, we want to add it back to the queue
if (response === 0) {
_this._mixpanel['persistence']._add_to_people_queue(action, queued_data);
}
if (!_.isUndefined(callback)) {
callback(response, data);
}
});
}
};
// Flush queued engage operations - order does not matter,
// and there are network level race conditions anyway
MixpanelPeople.prototype._flush = function(
_set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback
) {
var _this = this;
var $append_queue = this._mixpanel['persistence']._get_queue(APPEND_ACTION);
this._flush_one_queue(SET_ACTION, this.set, _set_callback);
this._flush_one_queue(SET_ONCE_ACTION, this.set_once, _set_once_callback);
this._flush_one_queue(UNSET_ACTION, this.unset, _unset_callback, function(queue) { return _.keys(queue); });
this._flush_one_queue(ADD_ACTION, this.increment, _add_callback);
this._flush_one_queue(UNION_ACTION, this.union, _union_callback);
// we have to fire off each $append individually since there is
// no concat method server side
if (!_.isUndefined($append_queue) && _.isArray($append_queue) && $append_queue.length) {
var $append_item;
var callback = function(response, data) {
if (response === 0) {
_this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, $append_item);
}
if (!_.isUndefined(_append_callback)) {
_append_callback(response, data);
}
};
for (var i = $append_queue.length - 1; i >= 0; i--) {
$append_item = $append_queue.pop();
_this.append($append_item, callback);
}
// Save the shortened append queue
_this._mixpanel['persistence'].save();
}
};
MixpanelPeople.prototype._is_reserved_property = function(prop) {
return prop === '$distinct_id' || prop === '$token';
};
// Internal class for notification display
MixpanelLib._Notification = function(notif_data, mixpanel_instance) {
_.bind_instance_methods(this);
this.mixpanel = mixpanel_instance;
this.persistence = this.mixpanel['persistence'];
this.campaign_id = _.escapeHTML(notif_data['id']);
this.message_id = _.escapeHTML(notif_data['message_id']);
this.body = (_.escapeHTML(notif_data['body']) || '').replace(/\n/g, '<br/>');
this.cta = _.escapeHTML(notif_data['cta']) || 'Close';
this.notif_type = _.escapeHTML(notif_data['type']) || 'takeover';
this.style = _.escapeHTML(notif_data['style']) || 'light';
this.title = _.escapeHTML(notif_data['title']) || '';
this.video_width = MPNotif.VIDEO_WIDTH;
this.video_height = MPNotif.VIDEO_HEIGHT;
// These fields are url-sanitized in the backend already.
this.dest_url = notif_data['cta_url'] || null;
this.image_url = notif_data['image_url'] || null;
this.thumb_image_url = notif_data['thumb_image_url'] || null;
this.video_url = notif_data['video_url'] || null;
this.clickthrough = true;
if (!this.dest_url) {
this.dest_url = '#dismiss';
this.clickthrough = false;
}
this.mini = this.notif_type === 'mini';
if (!this.mini) {
this.notif_type = 'takeover';
}
this.notif_width = !this.mini ? MPNotif.NOTIF_WIDTH : MPNotif.NOTIF_WIDTH_MINI;
this._set_client_config();
this.imgs_to_preload = this._init_image_html();
this._init_video();
};
MPNotif = MixpanelLib._Notification;
MPNotif.ANIM_TIME = 200;
MPNotif.MARKUP_PREFIX = 'mixpanel-notification';
MPNotif.BG_OPACITY = 0.6;
MPNotif.NOTIF_TOP = 25;
MPNotif.NOTIF_START_TOP = 200;
MPNotif.NOTIF_WIDTH = 388;
MPNotif.NOTIF_WIDTH_MINI = 420;
MPNotif.NOTIF_HEIGHT_MINI = 85;
MPNotif.THUMB_BORDER_SIZE = 5;
MPNotif.THUMB_IMG_SIZE = 60;
MPNotif.THUMB_OFFSET = Math.round(MPNotif.THUMB_IMG_SIZE / 2);
MPNotif.VIDEO_WIDTH = 595;
MPNotif.VIDEO_HEIGHT = 334;
MPNotif.prototype.show = function() {
var self = this;
this._set_client_config();
// don't display until HTML body exists
if (!this.body_el) {
setTimeout(function() { self.show(); }, 300);
return;
}
this._init_styles();
this._init_notification_el();
// wait for any images to load before showing notification
this._preload_images(this._attach_and_animate);
};
MPNotif.prototype.dismiss = _.safewrap(function() {
if (!this.marked_as_shown) {
// unexpected condition: user interacted with notif even though we didn't consider it
// visible (see _mark_as_shown()); send tracking signals to mark delivery
this._mark_delivery({'invisible': true});
}
var exiting_el = this.showing_video ? this._get_el('video') : this._get_notification_display_el();
if (this.use_transitions) {
this._remove_class('bg', 'visible');
this._add_class(exiting_el, 'exiting');
setTimeout(this._remove_notification_el, MPNotif.ANIM_TIME);
} else {
var notif_attr, notif_start, notif_goal;
if (this.mini) {
notif_attr = 'right';
notif_start = 20;
notif_goal = -100;
} else {
notif_attr = 'top';
notif_start = MPNotif.NOTIF_TOP;
notif_goal = MPNotif.NOTIF_START_TOP + MPNotif.NOTIF_TOP;
}
this._animate_els([
{
el: this._get_el('bg'),
attr: 'opacity',
start: MPNotif.BG_OPACITY,
goal: 0.0
},
{
el: exiting_el,
attr: 'opacity',
start: 1.0,
goal: 0.0
},
{
el: exiting_el,
attr: notif_attr,
start: notif_start,
goal: notif_goal
}
], MPNotif.ANIM_TIME, this._remove_notification_el);
}
});
MPNotif.prototype._add_class = _.safewrap(function(el, class_name) {
class_name = MPNotif.MARKUP_PREFIX + '-' + class_name;
if (typeof el === 'string') {
el = this._get_el(el);
}
if (!el.className) {
el.className = class_name;
} else if (!~(' ' + el.className + ' ').indexOf(' ' + class_name + ' ')) {
el.className += ' ' + class_name;
}
});
MPNotif.prototype._remove_class = _.safewrap(function(el, class_name) {
class_name = MPNotif.MARKUP_PREFIX + '-' + class_name;
if (typeof el === 'string') {
el = this._get_el(el);
}
if (el.className) {
el.className = (' ' + el.className + ' ')
.replace(' ' + class_name + ' ', '')
.replace(/^[\s\xA0]+/, '')
.replace(/[\s\xA0]+$/, '');
}
});
MPNotif.prototype._animate_els = _.safewrap(function(anims, mss, done_cb, start_time) {
var self = this,
in_progress = false,
ai, anim,
cur_time = 1 * new Date(), time_diff;
start_time = start_time || cur_time;
time_diff = cur_time - start_time;
for (ai = 0; ai < anims.length; ai++) {
anim = anims[ai];
if (typeof anim.val === 'undefined') {
anim.val = anim.start;
}
if (anim.val !== anim.goal) {
in_progress = true;
var anim_diff = anim.goal - anim.start,
anim_dir = anim.goal >= anim.start ? 1 : -1;
anim.val = anim.start + anim_diff * time_diff / mss;
if (anim.attr !== 'opacity') {
anim.val = Math.round(anim.val);
}
if ((anim_dir > 0 && anim.val >= anim.goal) || (anim_dir < 0 && anim.val <= anim.goal)) {
anim.val = anim.goal;
}
}
}
if (!in_progress) {
if (done_cb) {
done_cb();
}
return;
}
for (ai = 0; ai < anims.length; ai++) {
anim = anims[ai];
if (anim.el) {
var suffix = anim.attr === 'opacity' ? '' : 'px';
anim.el.style[anim.attr] = String(anim.val) + suffix;
}
}
setTimeout(function() { self._animate_els(anims, mss, done_cb, start_time); }, 10);
});
MPNotif.prototype._attach_and_animate = _.safewrap(function() {
var self = this;
// no possibility to double-display
if (this.shown || this._get_shown_campaigns()[this.campaign_id]) {
return;
}
this.shown = true;
this.body_el.appendChild(this.notification_el);
setTimeout(function() {
var notif_el = self._get_notification_display_el();
if (self.use_transitions) {
if (!self.mini) {
self._add_class('bg', 'visible');
}
self._add_class(notif_el, 'visible');
self._mark_as_shown();
} else {
var notif_attr, notif_start, notif_goal;
if (self.mini) {
notif_attr = 'right';
notif_start = -100;
notif_goal = 20;
} else {
notif_attr = 'top';
notif_start = MPNotif.NOTIF_START_TOP + MPNotif.NOTIF_TOP;
notif_goal = MPNotif.NOTIF_TOP;
}
self._animate_els([
{
el: self._get_el('bg'),
attr: 'opacity',
start: 0.0,
goal: MPNotif.BG_OPACITY
},
{
el: notif_el,
attr: 'opacity',
start: 0.0,
goal: 1.0
},
{
el: notif_el,
attr: notif_attr,
start: notif_start,
goal: notif_goal
}
], MPNotif.ANIM_TIME, self._mark_as_shown);
}
}, 100);
_.register_event(self._get_el('cancel'), 'click', function(e) {
e.preventDefault();
self.dismiss();
});
var click_el = self._get_el('button') ||
self._get_el('mini-content');
_.register_event(click_el, 'click', function(e) {
e.preventDefault();
if (self.show_video) {
self._track_event('$campaign_open', {'$resource_type': 'video'});
self._switch_to_video();
} else {
self.dismiss();
if (self.clickthrough) {
self._track_event('$campaign_open', {'$resource_type': 'link'}, function() {
window$1.location.href = self.dest_url;
});
}
}
});
});
MPNotif.prototype._get_el = function(id) {
return document$1.getElementById(MPNotif.MARKUP_PREFIX + '-' + id);
};
MPNotif.prototype._get_notification_display_el = function() {
return this._get_el(this.notif_type);
};
MPNotif.prototype._get_shown_campaigns = function() {
return this.persistence['props'][CAMPAIGN_IDS_KEY] || (this.persistence['props'][CAMPAIGN_IDS_KEY] = {});
};
MPNotif.prototype._browser_lte = function(browser, version) {
return this.browser_versions[browser] && this.browser_versions[browser] <= version;
};
MPNotif.prototype._init_image_html = function() {
var imgs_to_preload = [];
if (!this.mini) {
if (this.image_url) {
imgs_to_preload.push(this.image_url);
this.img_html = '<img id="img" src="' + this.image_url + '"/>';
} else {
this.img_html = '';
}
if (this.thumb_image_url) {
imgs_to_preload.push(this.thumb_image_url);
this.thumb_img_html =
'<div id="thumbborder-wrapper"><div id="thumbborder"></div></div>' +
'<img id="thumbnail"' +
' src="' + this.thumb_image_url + '"' +
' width="' + MPNotif.THUMB_IMG_SIZE + '"' +
' height="' + MPNotif.THUMB_IMG_SIZE + '"' +
'/>' +
'<div id="thumbspacer"></div>';
} else {
this.thumb_img_html = '';
}
} else {
this.thumb_image_url = this.thumb_image_url || '//cdn.mxpnl.com/site_media/images/icons/notifications/mini-news-dark.png';
imgs_to_preload.push(this.thumb_image_url);
}
return imgs_to_preload;
};
MPNotif.prototype._init_notification_el = function() {
var notification_html = '';
var video_src = '';
var video_html = '';
var cancel_html = '<div id="cancel">' +
'<div id="cancel-icon"></div>' +
'</div>';
this.notification_el = document$1.createElement('div');
this.notification_el.id = MPNotif.MARKUP_PREFIX + '-wrapper';
if (!this.mini) {
// TAKEOVER notification
var close_html = (this.clickthrough || this.show_video) ? '' : '<div id="button-close"></div>',
play_html = this.show_video ? '<div id="button-play"></div>' : '';
if (this._browser_lte('ie', 7)) {
close_html = '';
play_html = '';
}
notification_html =
'<div id="takeover">' +
this.thumb_img_html +
'<div id="mainbox">' +
cancel_html +
'<div id="content">' +
this.img_html +
'<div id="title">' + this.title + '</div>' +
'<div id="body">' + this.body + '</div>' +
'<div id="tagline">' +
'<a href="http://mixpanel.com?from=inapp" target="_blank">POWERED BY MIXPANEL</a>' +
'</div>' +
'</div>' +
'<div id="button">' +
close_html +
'<a id="button-link" href="' + this.dest_url + '">' + this.cta + '</a>' +
play_html +
'</div>' +
'</div>' +
'</div>';
} else {
// MINI notification
notification_html =
'<div id="mini">' +
'<div id="mainbox">' +
cancel_html +
'<div id="mini-content">' +
'<div id="mini-icon">' +
'<div id="mini-icon-img"></div>' +
'</div>' +
'<div id="body">' +
'<div id="body-text"><div>' + this.body + '</div></div>' +
'</div>' +
'</div>' +
'</div>' +
'<div id="mini-border"></div>' +
'</div>';
}
if (this.youtube_video) {
video_src = '//www.youtube.com/embed/' + this.youtube_video +
'?wmode=transparent&showinfo=0&modestbranding=0&rel=0&autoplay=1&loop=0&vq=hd1080';
if (this.yt_custom) {
video_src += '&enablejsapi=1&html5=1&controls=0';
video_html =
'<div id="video-controls">' +
'<div id="video-progress" class="video-progress-el">' +
'<div id="video-progress-total" class="video-progress-el"></div>' +
'<div id="video-elapsed" class="video-progress-el"></div>' +
'</div>' +
'<div id="video-time" class="video-progress-el"></div>' +
'</div>';
}
} else if (this.vimeo_video) {
video_src = '//player.vimeo.com/video/' + this.vimeo_video + '?autoplay=1&title=0&byline=0&portrait=0';
}
if (this.show_video) {
this.video_iframe =
'<iframe id="' + MPNotif.MARKUP_PREFIX + '-video-frame" ' +
'width="' + this.video_width + '" height="' + this.video_height + '" ' +
' src="' + video_src + '"' +
' frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen="1" scrolling="no"' +
'></iframe>';
video_html =
'<div id="video-' + (this.flip_animate ? '' : 'no') + 'flip">' +
'<div id="video">' +
'<div id="video-holder"></div>' +
video_html +
'</div>' +
'</div>';
}
var main_html = video_html + notification_html;
if (this.flip_animate) {
main_html =
(this.mini ? notification_html : '') +
'<div id="flipcontainer"><div id="flipper">' +
(this.mini ? video_html : main_html) +
'</div></div>';
}
this.notification_el.innerHTML =
('<div id="overlay" class="' + this.notif_type + '">' +
'<div id="campaignid-' + this.campaign_id + '">' +
'<div id="bgwrapper">' +
'<div id="bg"></div>' +
main_html +
'</div>' +
'</div>' +
'</div>')
.replace(/class=\"/g, 'class="' + MPNotif.MARKUP_PREFIX + '-')
.replace(/id=\"/g, 'id="' + MPNotif.MARKUP_PREFIX + '-');
};
MPNotif.prototype._init_styles = function() {
if (this.style === 'dark') {
this.style_vals = {
bg: '#1d1f25',
bg_actions: '#282b32',
bg_hover: '#3a4147',
bg_light: '#4a5157',
border_gray: '#32353c',
cancel_opacity: '0.4',
mini_hover: '#2a3137',
text_title: '#fff',
text_main: '#9498a3',
text_tagline: '#464851',
text_hover: '#ddd'
};
} else {
this.style_vals = {
bg: '#fff',
bg_actions: '#e7eaee',
bg_hover: '#eceff3',
bg_light: '#f5f5f5',
border_gray: '#e4ecf2',
cancel_opacity: '1.0',
mini_hover: '#fafafa',
text_title: '#5c6578',
text_main: '#8b949b',
text_tagline: '#ced9e6',
text_hover: '#7c8598'
};
}
var shadow = '0px 0px 35px 0px rgba(45, 49, 56, 0.7)',
video_shadow = shadow,
mini_shadow = shadow,
thumb_total_size = MPNotif.THUMB_IMG_SIZE + MPNotif.THUMB_BORDER_SIZE * 2,
anim_seconds = (MPNotif.ANIM_TIME / 1000) + 's';
if (this.mini) {
shadow = 'none';
}
// don't display on small viewports
var notif_media_queries = {},
min_width = MPNotif.NOTIF_WIDTH_MINI + 20;
notif_media_queries['@media only screen and (max-width: ' + (min_width - 1) + 'px)'] = {
'#overlay': {
'display': 'none'
}
};
var notif_styles = {
'.flipped': {
'transform': 'rotateY(180deg)'
},
'#overlay': {
'position': 'fixed',
'top': '0',
'left': '0',
'width': '100%',
'height': '100%',
'overflow': 'auto',
'text-align': 'center',
'z-index': '10000',
'font-family': '"Helvetica", "Arial", sans-serif',
'-webkit-font-smoothing': 'antialiased',
'-moz-osx-font-smoothing': 'grayscale'
},
'#overlay.mini': {
'height': '0',
'overflow': 'visible'
},
'#overlay a': {
'width': 'initial',
'padding': '0',
'text-decoration': 'none',
'text-transform': 'none',
'color': 'inherit'
},
'#bgwrapper': {
'position': 'relative',
'width': '100%',
'height': '100%'
},
'#bg': {
'position': 'fixed',
'top': '0',
'left': '0',
'width': '100%',
'height': '100%',
'min-width': this.doc_width * 4 + 'px',
'min-height': this.doc_height * 4 + 'px',
'background-color': 'black',
'opacity': '0.0',
'-ms-filter': 'progid:DXImageTransform.Microsoft.Alpha(Opacity=60)', // IE8
'filter': 'alpha(opacity=60)', // IE5-7
'transition': 'opacity ' + anim_seconds
},
'#bg.visible': {
'opacity': MPNotif.BG_OPACITY
},
'.mini #bg': {
'width': '0',
'height': '0',
'min-width': '0'
},
'#flipcontainer': {
'perspective': '1000px',
'position': 'absolute',
'width': '100%'
},
'#flipper': {
'position': 'relative',
'transform-style': 'preserve-3d',
'transition': '0.3s'
},
'#takeover': {
'position': 'absolute',
'left': '50%',
'width': MPNotif.NOTIF_WIDTH + 'px',
'margin-left': Math.round(-MPNotif.NOTIF_WIDTH / 2) + 'px',
'backface-visibility': 'hidden',
'transform': 'rotateY(0deg)',
'opacity': '0.0',
'top': MPNotif.NOTIF_START_TOP + 'px',
'transition': 'opacity ' + anim_seconds + ', top ' + anim_seconds
},
'#takeover.visible': {
'opacity': '1.0',
'top': MPNotif.NOTIF_TOP + 'px'
},
'#takeover.exiting': {
'opacity': '0.0',
'top': MPNotif.NOTIF_START_TOP + 'px'
},
'#thumbspacer': {
'height': MPNotif.THUMB_OFFSET + 'px'
},
'#thumbborder-wrapper': {
'position': 'absolute',
'top': (-MPNotif.THUMB_BORDER_SIZE) + 'px',
'left': (MPNotif.NOTIF_WIDTH / 2 - MPNotif.THUMB_OFFSET - MPNotif.THUMB_BORDER_SIZE) + 'px',
'width': thumb_total_size + 'px',
'height': (thumb_total_size / 2) + 'px',
'overflow': 'hidden'
},
'#thumbborder': {
'position': 'absolute',
'width': thumb_total_size + 'px',
'height': thumb_total_size + 'px',
'border-radius': thumb_total_size + 'px',
'background-color': this.style_vals.bg_actions,
'opacity': '0.5'
},
'#thumbnail': {
'position': 'absolute',
'top': '0px',
'left': (MPNotif.NOTIF_WIDTH / 2 - MPNotif.THUMB_OFFSET) + 'px',
'width': MPNotif.THUMB_IMG_SIZE + 'px',
'height': MPNotif.THUMB_IMG_SIZE + 'px',
'overflow': 'hidden',
'z-index': '100',
'border-radius': MPNotif.THUMB_IMG_SIZE + 'px'
},
'#mini': {
'position': 'absolute',
'right': '20px',
'top': MPNotif.NOTIF_TOP + 'px',
'width': this.notif_width + 'px',
'height': MPNotif.NOTIF_HEIGHT_MINI * 2 + 'px',
'margin-top': 20 - MPNotif.NOTIF_HEIGHT_MINI + 'px',
'backface-visibility': 'hidden',
'opacity': '0.0',
'transform': 'rotateX(90deg)',
'transition': 'opacity 0.3s, transform 0.3s, right 0.3s'
},
'#mini.visible': {
'opacity': '1.0',
'transform': 'rotateX(0deg)'
},
'#mini.exiting': {
'opacity': '0.0',
'right': '-150px'
},
'#mainbox': {
'border-radius': '4px',
'box-shadow': shadow,
'text-align': 'center',
'background-color': this.style_vals.bg,
'font-size': '14px',
'color': this.style_vals.text_main
},
'#mini #mainbox': {
'height': MPNotif.NOTIF_HEIGHT_MINI + 'px',
'margin-top': MPNotif.NOTIF_HEIGHT_MINI + 'px',
'border-radius': '3px',
'transition': 'background-color ' + anim_seconds
},
'#mini-border': {
'height': (MPNotif.NOTIF_HEIGHT_MINI + 6) + 'px',
'width': (MPNotif.NOTIF_WIDTH_MINI + 6) + 'px',
'position': 'absolute',
'top': '-3px',
'left': '-3px',
'margin-top': MPNotif.NOTIF_HEIGHT_MINI + 'px',
'border-radius': '6px',
'opacity': '0.25',
'background-color': '#fff',
'z-index': '-1',
'box-shadow': mini_shadow
},
'#mini-icon': {
'position': 'relative',
'display': 'inline-block',
'width': '75px',
'height': MPNotif.NOTIF_HEIGHT_MINI + 'px',
'border-radius': '3px 0 0 3px',
'background-color': this.style_vals.bg_actions,
'background': 'linear-gradient(135deg, ' + this.style_vals.bg_light + ' 0%, ' + this.style_vals.bg_actions + ' 100%)',
'transition': 'background-color ' + anim_seconds
},
'#mini:hover #mini-icon': {
'background-color': this.style_vals.mini_hover
},
'#mini:hover #mainbox': {
'background-color': this.style_vals.mini_hover
},
'#mini-icon-img': {
'position': 'absolute',
'background-image': 'url(' + this.thumb_image_url + ')',
'width': '48px',
'height': '48px',
'top': '20px',
'left': '12px'
},
'#content': {
'padding': '30px 20px 0px 20px'
},
'#mini-content': {
'text-align': 'left',
'height': MPNotif.NOTIF_HEIGHT_MINI + 'px',
'cursor': 'pointer'
},
'#img': {
'width': '328px',
'margin-top': '30px',
'border-radius': '5px'
},
'#title': {
'max-height': '600px',
'overflow': 'hidden',
'word-wrap': 'break-word',
'padding': '25px 0px 20px 0px',
'font-size': '19px',
'font-weight': 'bold',
'color': this.style_vals.text_title
},
'#body': {
'max-height': '600px',
'margin-bottom': '25px',
'overflow': 'hidden',
'word-wrap': 'break-word',
'line-height': '21px',
'font-size': '15px',
'font-weight': 'normal',
'text-align': 'left'
},
'#mini #body': {
'display': 'inline-block',
'max-width': '250px',
'margin': '0 0 0 30px',
'height': MPNotif.NOTIF_HEIGHT_MINI + 'px',
'font-size': '16px',
'letter-spacing': '0.8px',
'color': this.style_vals.text_title
},
'#mini #body-text': {
'display': 'table',
'height': MPNotif.NOTIF_HEIGHT_MINI + 'px'
},
'#mini #body-text div': {
'display': 'table-cell',
'vertical-align': 'middle'
},
'#tagline': {
'margin-bottom': '15px',
'font-size': '10px',
'font-weight': '600',
'letter-spacing': '0.8px',
'color': '#ccd7e0',
'text-align': 'left'
},
'#tagline a': {
'color': this.style_vals.text_tagline,
'transition': 'color ' + anim_seconds
},
'#tagline a:hover': {
'color': this.style_vals.text_hover
},
'#cancel': {
'position': 'absolute',
'right': '0',
'width': '8px',
'height': '8px',
'padding': '10px',
'border-radius': '20px',
'margin': '12px 12px 0 0',
'box-sizing': 'content-box',
'cursor': 'pointer',
'transition': 'background-color ' + anim_seconds
},
'#mini #cancel': {
'margin': '7px 7px 0 0'
},
'#cancel-icon': {
'width': '8px',
'height': '8px',
'overflow': 'hidden',
'background-image': 'url(//cdn.mxpnl.com/site_media/images/icons/notifications/cancel-x.png)',
'opacity': this.style_vals.cancel_opacity
},
'#cancel:hover': {
'background-color': this.style_vals.bg_hover
},
'#button': {
'display': 'block',
'height': '60px',
'line-height': '60px',
'text-align': 'center',
'background-color': this.style_vals.bg_actions,
'border-radius': '0 0 4px 4px',
'overflow': 'hidden',
'cursor': 'pointer',
'transition': 'background-color ' + anim_seconds
},
'#button-close': {
'display': 'inline-block',
'width': '9px',
'height': '60px',
'margin-right': '8px',
'vertical-align': 'top',
'background-image': 'url(//cdn.mxpnl.com/site_media/images/icons/notifications/close-x-' + this.style + '.png)',
'background-repeat': 'no-repeat',
'background-position': '0px 25px'
},
'#button-play': {
'display': 'inline-block',
'width': '30px',
'height': '60px',
'margin-left': '15px',
'background-image': 'url(//cdn.mxpnl.com/site_media/images/icons/notifications/play-' + this.style + '-small.png)',
'background-repeat': 'no-repeat',
'background-position': '0px 15px'
},
'a#button-link': {
'display': 'inline-block',
'vertical-align': 'top',
'text-align': 'center',
'font-size': '17px',
'font-weight': 'bold',
'overflow': 'hidden',
'word-wrap': 'break-word',
'color': this.style_vals.text_title,
'transition': 'color ' + anim_seconds
},
'#button:hover': {
'background-color': this.style_vals.bg_hover,
'color': this.style_vals.text_hover
},
'#button:hover a': {
'color': this.style_vals.text_hover
},
'#video-noflip': {
'position': 'relative',
'top': (-this.video_height * 2) + 'px'
},
'#video-flip': {
'backface-visibility': 'hidden',
'transform': 'rotateY(180deg)'
},
'#video': {
'position': 'absolute',
'width': (this.video_width - 1) + 'px',
'height': this.video_height + 'px',
'top': MPNotif.NOTIF_TOP + 'px',
'margin-top': '100px',
'left': '50%',
'margin-left': Math.round(-this.video_width / 2) + 'px',
'overflow': 'hidden',
'border-radius': '5px',
'box-shadow': video_shadow,
'transform': 'translateZ(1px)', // webkit rendering bug http://stackoverflow.com/questions/18167981/clickable-link-area-unexpectedly-smaller-after-css-transform
'transition': 'opacity ' + anim_seconds + ', top ' + anim_seconds
},
'#video.exiting': {
'opacity': '0.0',
'top': this.video_height + 'px'
},
'#video-holder': {
'position': 'absolute',
'width': (this.video_width - 1) + 'px',
'height': this.video_height + 'px',
'overflow': 'hidden',
'border-radius': '5px'
},
'#video-frame': {
'margin-left': '-1px',
'width': this.video_width + 'px'
},
'#video-controls': {
'opacity': '0',
'transition': 'opacity 0.5s'
},
'#video:hover #video-controls': {
'opacity': '1.0'
},
'#video .video-progress-el': {
'position': 'absolute',
'bottom': '0',
'height': '25px',
'border-radius': '0 0 0 5px'
},
'#video-progress': {
'width': '90%'
},
'#video-progress-total': {
'width': '100%',
'background-color': this.style_vals.bg,
'opacity': '0.7'
},
'#video-elapsed': {
'width': '0',
'background-color': '#6cb6f5',
'opacity': '0.9'
},
'#video #video-time': {
'width': '10%',
'right': '0',
'font-size': '11px',
'line-height': '25px',
'color': this.style_vals.text_main,
'background-color': '#666',
'border-radius': '0 0 5px 0'
}
};
// IE hacks
if (this._browser_lte('ie', 8)) {
_.extend(notif_styles, {
'* html #overlay': {
'position': 'absolute'
},
'* html #bg': {
'position': 'absolute'
},
'html, body': {
'height': '100%'
}
});
}
if (this._browser_lte('ie', 7)) {
_.extend(notif_styles, {
'#mini #body': {
'display': 'inline',
'zoom': '1',
'border': '1px solid ' + this.style_vals.bg_hover
},
'#mini #body-text': {
'padding': '20px'
},
'#mini #mini-icon': {
'display': 'none'
}
});
}
// add vendor-prefixed style rules
var VENDOR_STYLES = ['backface-visibility', 'border-radius', 'box-shadow', 'opacity',
'perspective', 'transform', 'transform-style', 'transition'],
VENDOR_PREFIXES = ['khtml', 'moz', 'ms', 'o', 'webkit'];
for (var selector in notif_styles) {
for (var si = 0; si < VENDOR_STYLES.length; si++) {
var prop = VENDOR_STYLES[si];
if (prop in notif_styles[selector]) {
var val = notif_styles[selector][prop];
for (var pi = 0; pi < VENDOR_PREFIXES.length; pi++) {
notif_styles[selector]['-' + VENDOR_PREFIXES[pi] + '-' + prop] = val;
}
}
}
}
var inject_styles = function(styles, media_queries) {
var create_style_text = function(style_defs) {
var st = '';
for (var selector in style_defs) {
var mp_selector = selector
.replace(/#/g, '#' + MPNotif.MARKUP_PREFIX + '-')
.replace(/\./g, '.' + MPNotif.MARKUP_PREFIX + '-');
st += '\n' + mp_selector + ' {';
var props = style_defs[selector];
for (var k in props) {
st += k + ':' + props[k] + ';';
}
st += '}';
}
return st;
};
var create_media_query_text = function(mq_defs) {
var mqt = '';
for (var mq in mq_defs) {
mqt += '\n' + mq + ' {' + create_style_text(mq_defs[mq]) + '\n}';
}
return mqt;
};
var style_text = create_style_text(styles) + create_media_query_text(media_queries),
head_el = document$1.head || document$1.getElementsByTagName('head')[0] || document$1.documentElement,
style_el = document$1.createElement('style');
head_el.appendChild(style_el);
style_el.setAttribute('type', 'text/css');
if (style_el.styleSheet) { // IE
style_el.styleSheet.cssText = style_text;
} else {
style_el.textContent = style_text;
}
};
inject_styles(notif_styles, notif_media_queries);
};
MPNotif.prototype._init_video = _.safewrap(function() {
if (!this.video_url) {
return;
}
var self = this;
// Youtube iframe API compatibility
self.yt_custom = 'postMessage' in window$1;
self.dest_url = self.video_url;
var youtube_match = self.video_url.match(
// http://stackoverflow.com/questions/2936467/parse-youtube-video-id-using-preg-match
/(?:youtube(?:-nocookie)?\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/i
),
vimeo_match = self.video_url.match(
/vimeo\.com\/.*?(\d+)/i
);
if (youtube_match) {
self.show_video = true;
self.youtube_video = youtube_match[1];
if (self.yt_custom) {
window$1['onYouTubeIframeAPIReady'] = function() {
if (self._get_el('video-frame')) {
self._yt_video_ready();
}
};
// load Youtube iframe API; see https://developers.google.com/youtube/iframe_api_reference
var tag = document$1.createElement('script');
tag.src = '//www.youtube.com/iframe_api';
var firstScriptTag = document$1.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
}
} else if (vimeo_match) {
self.show_video = true;
self.vimeo_video = vimeo_match[1];
}
// IE <= 7, FF <= 3: fall through to video link rather than embedded player
if (self._browser_lte('ie', 7) || self._browser_lte('firefox', 3)) {
self.show_video = false;
self.clickthrough = true;
}
});
MPNotif.prototype._mark_as_shown = _.safewrap(function() {
// click on background to dismiss
var self = this;
_.register_event(self._get_el('bg'), 'click', function() {
self.dismiss();
});
var get_style = function(el, style_name) {
var styles = {};
if (document$1.defaultView && document$1.defaultView.getComputedStyle) {
styles = document$1.defaultView.getComputedStyle(el, null); // FF3 requires both args
} else if (el.currentStyle) { // IE
styles = el.currentStyle;
}
return styles[style_name];
};
if (this.campaign_id) {
var notif_el = this._get_el('overlay');
if (notif_el && get_style(notif_el, 'visibility') !== 'hidden' && get_style(notif_el, 'display') !== 'none') {
this._mark_delivery();
}
}
});
MPNotif.prototype._mark_delivery = _.safewrap(function(extra_props) {
if (!this.marked_as_shown) {
this.marked_as_shown = true;
if (this.campaign_id) {
// mark notification shown (local cache)
this._get_shown_campaigns()[this.campaign_id] = 1 * new Date();
this.persistence.save();
}
// track delivery
this._track_event('$campaign_delivery', extra_props);
// mark notification shown (mixpanel property)
this.mixpanel['people']['append']({
'$campaigns': this.campaign_id,
'$notifications': {
'campaign_id': this.campaign_id,
'message_id': this.message_id,
'type': 'web',
'time': new Date()
}
});
}
});
MPNotif.prototype._preload_images = function(all_loaded_cb) {
var self = this;
if (this.imgs_to_preload.length === 0) {
all_loaded_cb();
return;
}
var preloaded_imgs = 0;
var img_objs = [];
var onload = function() {
preloaded_imgs++;
if (preloaded_imgs === self.imgs_to_preload.length && all_loaded_cb) {
all_loaded_cb();
all_loaded_cb = null;
}
};
for (var i = 0; i < this.imgs_to_preload.length; i++) {
var img = new Image();
img.onload = onload;
img.src = this.imgs_to_preload[i];
if (img.complete) {
onload();
}
img_objs.push(img);
}
// IE6/7 doesn't fire onload reliably
if (this._browser_lte('ie', 7)) {
setTimeout(function() {
var imgs_loaded = true;
for (i = 0; i < img_objs.length; i++) {
if (!img_objs[i].complete) {
imgs_loaded = false;
}
}
if (imgs_loaded && all_loaded_cb) {
all_loaded_cb();
all_loaded_cb = null;
}
}, 500);
}
};
MPNotif.prototype._remove_notification_el = _.safewrap(function() {
window$1.clearInterval(this._video_progress_checker);
this.notification_el.style.visibility = 'hidden';
this.body_el.removeChild(this.notification_el);
});
MPNotif.prototype._set_client_config = function() {
var get_browser_version = function(browser_ex) {
var match = navigator.userAgent.match(browser_ex);
return match && match[1];
};
this.browser_versions = {};
this.browser_versions['chrome'] = get_browser_version(/Chrome\/(\d+)/);
this.browser_versions['firefox'] = get_browser_version(/Firefox\/(\d+)/);
this.browser_versions['ie'] = get_browser_version(/MSIE (\d+).+/);
if (!this.browser_versions['ie'] && !(window$1.ActiveXObject) && 'ActiveXObject' in window$1) {
this.browser_versions['ie'] = 11;
}
this.body_el = document$1.body || document$1.getElementsByTagName('body')[0];
if (this.body_el) {
this.doc_width = Math.max(
this.body_el.scrollWidth, document$1.documentElement.scrollWidth,
this.body_el.offsetWidth, document$1.documentElement.offsetWidth,
this.body_el.clientWidth, document$1.documentElement.clientWidth
);
this.doc_height = Math.max(
this.body_el.scrollHeight, document$1.documentElement.scrollHeight,
this.body_el.offsetHeight, document$1.documentElement.offsetHeight,
this.body_el.clientHeight, document$1.documentElement.clientHeight
);
}
// detect CSS compatibility
var ie_ver = this.browser_versions['ie'];
var sample_styles = document$1.createElement('div').style,
is_css_compatible = function(rule) {
if (rule in sample_styles) {
return true;
}
if (!ie_ver) {
rule = rule[0].toUpperCase() + rule.slice(1);
var props = ['O' + rule, 'Webkit' + rule, 'Moz' + rule];
for (var i = 0; i < props.length; i++) {
if (props[i] in sample_styles) {
return true;
}
}
}
return false;
};
this.use_transitions = this.body_el &&
is_css_compatible('transition') &&
is_css_compatible('transform');
this.flip_animate = (this.browser_versions['chrome'] >= 33 || this.browser_versions['firefox'] >= 15) &&
this.body_el &&
is_css_compatible('backfaceVisibility') &&
is_css_compatible('perspective') &&
is_css_compatible('transform');
};
MPNotif.prototype._switch_to_video = _.safewrap(function() {
var self = this,
anims = [
{
el: self._get_notification_display_el(),
attr: 'opacity',
start: 1.0,
goal: 0.0
},
{
el: self._get_notification_display_el(),
attr: 'top',
start: MPNotif.NOTIF_TOP,
goal: -500
},
{
el: self._get_el('video-noflip'),
attr: 'opacity',
start: 0.0,
goal: 1.0
},
{
el: self._get_el('video-noflip'),
attr: 'top',
start: -self.video_height * 2,
goal: 0
}
];
if (self.mini) {
var bg = self._get_el('bg'),
overlay = self._get_el('overlay');
bg.style.width = '100%';
bg.style.height = '100%';
overlay.style.width = '100%';
self._add_class(self._get_notification_display_el(), 'exiting');
self._add_class(bg, 'visible');
anims.push({
el: self._get_el('bg'),
attr: 'opacity',
start: 0.0,
goal: MPNotif.BG_OPACITY
});
}
var video_el = self._get_el('video-holder');
video_el.innerHTML = self.video_iframe;
var video_ready = function() {
if (window$1['YT'] && window$1['YT']['loaded']) {
self._yt_video_ready();
}
self.showing_video = true;
self._get_notification_display_el().style.visibility = 'hidden';
};
if (self.flip_animate) {
self._add_class('flipper', 'flipped');
setTimeout(video_ready, MPNotif.ANIM_TIME);
} else {
self._animate_els(anims, MPNotif.ANIM_TIME, video_ready);
}
});
MPNotif.prototype._track_event = function(event_name, properties, cb) {
if (this.campaign_id) {
properties = properties || {};
properties = _.extend(properties, {
'campaign_id': this.campaign_id,
'message_id': this.message_id,
'message_type': 'web_inapp',
'message_subtype': this.notif_type
});
this.mixpanel['track'](event_name, properties, cb);
} else if (cb) {
cb.call();
}
};
MPNotif.prototype._yt_video_ready = _.safewrap(function() {
var self = this;
if (self.video_inited) {
return;
}
self.video_inited = true;
var progress_bar = self._get_el('video-elapsed'),
progress_time = self._get_el('video-time'),
progress_el = self._get_el('video-progress');
new window$1['YT']['Player'](MPNotif.MARKUP_PREFIX + '-video-frame', {
'events': {
'onReady': function(event) {
var ytplayer = event['target'],
video_duration = ytplayer['getDuration'](),
pad = function(i) {
return ('00' + i).slice(-2);
},
update_video_time = function(current_time) {
var secs = Math.round(video_duration - current_time),
mins = Math.floor(secs / 60),
hours = Math.floor(mins / 60);
secs -= mins * 60;
mins -= hours * 60;
progress_time.innerHTML = '-' + (hours ? hours + ':' : '') + pad(mins) + ':' + pad(secs);
};
update_video_time(0);
self._video_progress_checker = window$1.setInterval(function() {
var current_time = ytplayer['getCurrentTime']();
progress_bar.style.width = (current_time / video_duration * 100) + '%';
update_video_time(current_time);
}, 250);
_.register_event(progress_el, 'click', function(e) {
var clickx = Math.max(0, e.pageX - progress_el.getBoundingClientRect().left);
ytplayer['seekTo'](video_duration * clickx / progress_el.clientWidth, true);
});
}
}
});
});
// EXPORTS (for closure compiler)
// MixpanelLib Exports
MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
MixpanelLib.prototype['track_links'] = MixpanelLib.prototype.track_links;
MixpanelLib.prototype['track_forms'] = MixpanelLib.prototype.track_forms;
MixpanelLib.prototype['track_pageview'] = MixpanelLib.prototype.track_pageview;
MixpanelLib.prototype['register'] = MixpanelLib.prototype.register;
MixpanelLib.prototype['register_once'] = MixpanelLib.prototype.register_once;
MixpanelLib.prototype['unregister'] = MixpanelLib.prototype.unregister;
MixpanelLib.prototype['identify'] = MixpanelLib.prototype.identify;
MixpanelLib.prototype['alias'] = MixpanelLib.prototype.alias;
MixpanelLib.prototype['name_tag'] = MixpanelLib.prototype.name_tag;
MixpanelLib.prototype['set_config'] = MixpanelLib.prototype.set_config;
MixpanelLib.prototype['get_config'] = MixpanelLib.prototype.get_config;
MixpanelLib.prototype['get_property'] = MixpanelLib.prototype.get_property;
MixpanelLib.prototype['get_distinct_id'] = MixpanelLib.prototype.get_distinct_id;
MixpanelLib.prototype['toString'] = MixpanelLib.prototype.toString;
MixpanelLib.prototype['_check_and_handle_notifications'] = MixpanelLib.prototype._check_and_handle_notifications;
MixpanelLib.prototype['_show_notification'] = MixpanelLib.prototype._show_notification;
MixpanelLib.prototype['opt_out_tracking'] = MixpanelLib.prototype.opt_out_tracking;
MixpanelLib.prototype['opt_in_tracking'] = MixpanelLib.prototype.opt_in_tracking;
MixpanelLib.prototype['has_opted_out_tracking'] = MixpanelLib.prototype.has_opted_out_tracking;
MixpanelLib.prototype['has_opted_in_tracking'] = MixpanelLib.prototype.has_opted_in_tracking;
MixpanelLib.prototype['clear_opt_in_out_tracking'] = MixpanelLib.prototype.clear_opt_in_out_tracking;
// MixpanelPersistence Exports
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
MixpanelPersistence.prototype['update_search_keyword'] = MixpanelPersistence.prototype.update_search_keyword;
MixpanelPersistence.prototype['update_referrer_info'] = MixpanelPersistence.prototype.update_referrer_info;
MixpanelPersistence.prototype['get_cross_subdomain'] = MixpanelPersistence.prototype.get_cross_subdomain;
MixpanelPersistence.prototype['clear'] = MixpanelPersistence.prototype.clear;
// MixpanelPeople Exports
MixpanelPeople.prototype['set'] = MixpanelPeople.prototype.set;
MixpanelPeople.prototype['set_once'] = MixpanelPeople.prototype.set_once;
MixpanelPeople.prototype['unset'] = MixpanelPeople.prototype.unset;
MixpanelPeople.prototype['increment'] = MixpanelPeople.prototype.increment;
MixpanelPeople.prototype['append'] = MixpanelPeople.prototype.append;
MixpanelPeople.prototype['union'] = MixpanelPeople.prototype.union;
MixpanelPeople.prototype['track_charge'] = MixpanelPeople.prototype.track_charge;
MixpanelPeople.prototype['clear_charges'] = MixpanelPeople.prototype.clear_charges;
MixpanelPeople.prototype['delete_user'] = MixpanelPeople.prototype.delete_user;
MixpanelPeople.prototype['toString'] = MixpanelPeople.prototype.toString;
_.safewrap_class(MixpanelLib, ['identify', '_check_and_handle_notifications', '_show_notification']);
var instances = {};
var extend_mp = function() {
// add all the sub mixpanel instances
_.each(instances, function(instance, name) {
if (name !== PRIMARY_INSTANCE_NAME) { mixpanel_master[name] = instance; }
});
// add private functions as _
mixpanel_master['_'] = _;
};
var override_mp_init_func = function() {
// we override the snippets init function to handle the case where a
// user initializes the mixpanel library after the script loads & runs
mixpanel_master['init'] = function(token, config, name) {
if (name) {
// initialize a sub library
if (!mixpanel_master[name]) {
mixpanel_master[name] = instances[name] = create_mplib(token, config, name);
mixpanel_master[name]._loaded();
}
return mixpanel_master[name];
} else {
var instance = mixpanel_master;
if (instances[PRIMARY_INSTANCE_NAME]) {
// main mixpanel lib already initialized
instance = instances[PRIMARY_INSTANCE_NAME];
} else if (token) {
// intialize the main mixpanel lib
instance = create_mplib(token, config, PRIMARY_INSTANCE_NAME);
instance._loaded();
instances[PRIMARY_INSTANCE_NAME] = instance;
}
mixpanel_master = instance;
if (init_type === INIT_SNIPPET) {
window$1[PRIMARY_INSTANCE_NAME] = mixpanel_master;
}
extend_mp();
}
};
};
var add_dom_loaded_handler = function() {
// Cross browser DOM Loaded support
function dom_loaded_handler() {
// function flag since we only want to execute this once
if (dom_loaded_handler.done) { return; }
dom_loaded_handler.done = true;
DOM_LOADED = true;
ENQUEUE_REQUESTS = false;
_.each(instances, function(inst) {
inst._dom_loaded();
});
}
function do_scroll_check() {
try {
document$1.documentElement.doScroll('left');
} catch(e) {
setTimeout(do_scroll_check, 1);
return;
}
dom_loaded_handler();
}
if (document$1.addEventListener) {
if (document$1.readyState === 'complete') {
// safari 4 can fire the DOMContentLoaded event before loading all
// external JS (including this file). you will see some copypasta
// on the internet that checks for 'complete' and 'loaded', but
// 'loaded' is an IE thing
dom_loaded_handler();
} else {
document$1.addEventListener('DOMContentLoaded', dom_loaded_handler, false);
}
} else if (document$1.attachEvent) {
// IE
document$1.attachEvent('onreadystatechange', dom_loaded_handler);
// check to make sure we arn't in a frame
var toplevel = false;
try {
toplevel = window$1.frameElement === null;
} catch(e) {
// noop
}
if (document$1.documentElement.doScroll && toplevel) {
do_scroll_check();
}
}
// fallback handler, always will work
_.register_event(window$1, 'load', dom_loaded_handler, true);
};
function init_as_module() {
init_type = INIT_MODULE;
mixpanel_master = new MixpanelLib();
override_mp_init_func();
mixpanel_master['init']();
add_dom_loaded_handler();
return mixpanel_master;
}
var mixpanel = init_as_module();
return mixpanel;
}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment