Первая онлайн-школа креативного мышления для детей! 5+
/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ }
+ 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 support@mixpanel.com.');
+ 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
+ _.localStorage = {
+ 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 - crisp@xs4all.nl
+ // with input from Carl Sverre - mail@carlsverre.com
+ // 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': win.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);
+
+ /*
+ * 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 = (win.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,
+ 'property_blacklist': []
+ };
+
+ 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
+ win.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() {
+ win.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';
+ }
+
+ var localStorage_supported = function() {
+ 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');
+ }
+ return supported;
+ };
+ if (storage_type === 'localStorage' && localStorage_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();
+ }
+ };
+
+ 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);
+ }
+
+ try {
+ add_dom_event_counting_handlers(instance);
+ } catch (e) {
+ console$1.error(e);
+ }
+ }
+
+ // 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.
See a list of default config options.
+ * @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.register_once({'distinct_id': _.UUID()}, '');
+ };
+
+ // Private methods
+
+ 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);
+ _.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);
+ // 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 = 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
+ );
+
+ try {
+ if (this.get_config('autotrack') && event_name !== 'mp_page_view' && event_name !== '$create_alias') {
+ // The point of $__c is to count how many clicks occur per tracked event. Since we're
+ // tracking an event in this function, we need to reset the $__c value.
+ properties = _.extend({}, properties, this.mp_counts);
+ this.mp_counts = {'$__c': 0};
+ _.cookie.set('mp_' + this.get_config('name') + '__c', 0, 1, true);
+ }
+ } catch (e) {
+ console$1.error(e);
+ }
+
+ 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': 'jdoe@example.com',
+ * '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.
+ *
Learn more about how mixpanel.identify and mixpanel.alias can be used.
+ *
+ * @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
+ *
+ * // 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
+ * }
+ *
+ *
+ * @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 = 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();
+ };
+
+ 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 = 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 = 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 = 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 = 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 = 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 = 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, '
');
+ 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() {
+ win.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 = '

';
+ } else {
+ this.img_html = '';
+ }
+ if (this.thumb_image_url) {
+ imgs_to_preload.push(this.thumb_image_url);
+ this.thumb_img_html =
+ '
' +
+ '

' +
+ '
';
+ } 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 = '
';
+
+ 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) ? '' : '
',
+ play_html = this.show_video ? '
' : '';
+ if (this._browser_lte('ie', 7)) {
+ close_html = '';
+ play_html = '';
+ }
+ notification_html =
+ '
' +
+ this.thumb_img_html +
+ '
' +
+ cancel_html +
+ '
' +
+ this.img_html +
+ '
' + this.title + '
' +
+ '
' + this.body + '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
';
+ } else {
+ // MINI notification
+ notification_html =
+ '
' +
+ '
' +
+ cancel_html +
+ '
' +
+ '
' +
+ '
' +
+ '
';
+ }
+ 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 =
+ '
';
+ }
+ } 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 =
+ '
';
+ video_html =
+ '
' +
+ '
' +
+ '
' +
+ video_html +
+ '
' +
+ '
';
+ }
+ var main_html = video_html + notification_html;
+ if (this.flip_animate) {
+ main_html =
+ (this.mini ? notification_html : '') +
+ '
' +
+ (this.mini ? video_html : main_html) +
+ '
';
+ }
+
+ this.notification_el.innerHTML =
+ ('
' +
+ '
' +
+ '
' +
+ '
' +
+ main_html +
+ '
' +
+ '
' +
+ '
')
+ .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 win;
+
+ 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) {
+ win['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() {
+ win.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'] && !(win.ActiveXObject) && 'ActiveXObject' in win) {
+ 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 (win['YT'] && win['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 win['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 = win.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;
+
+ // 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) {
+ win[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 = win.frameElement === null;
+ } catch(e) {
+ // noop
+ }
+
+ if (document$1.documentElement.doScroll && toplevel) {
+ do_scroll_check();
+ }
+ }
+
+ // fallback handler, always will work
+ _.register_event(win, 'load', dom_loaded_handler, true);
+ };
+
+ var add_dom_event_counting_handlers = function(instance) {
+ var name = instance.get_config('name');
+
+ instance.mp_counts = instance.mp_counts || {};
+ instance.mp_counts['$__c'] = parseInt(_.cookie.get('mp_' + name + '__c')) || 0;
+
+ var increment_count = function() {
+ instance.mp_counts['$__c'] = (instance.mp_counts['$__c'] || 0) + 1;
+ _.cookie.set('mp_' + name + '__c', instance.mp_counts['$__c'], 1, true);
+ };
+
+ var evtCallback = function() {
+ try {
+ instance.mp_counts = instance.mp_counts || {};
+ increment_count();
+ } catch (e) {
+ console$1.error(e);
+ }
+ };
+ _.register_event(document$1, 'submit', evtCallback);
+ _.register_event(document$1, 'change', evtCallback);
+ var mousedownTarget = null;
+ _.register_event(document$1, 'mousedown', function(e) {
+ mousedownTarget = e.target;
+ });
+ _.register_event(document$1, 'mouseup', function(e) {
+ if (e.target === mousedownTarget) {
+ evtCallback(e);
+ }
+ });
+ };
+
+ function init_from_snippet() {
+ init_type = INIT_SNIPPET;
+ mixpanel_master = win[PRIMARY_INSTANCE_NAME];
+
+ // Initialization
+ if (_.isUndefined(mixpanel_master)) {
+ // mixpanel wasn't initialized properly, report error and quit
+ console$1.critical('"mixpanel" object not initialized. Ensure you are using the latest version of the Mixpanel JS Library along with the snippet we provide.');
+ return;
+ }
+ if (mixpanel_master['__loaded'] || (mixpanel_master['config'] && mixpanel_master['persistence'])) {
+ // lib has already been loaded at least once; we don't want to override the global object this time so bomb early
+ console$1.error('Mixpanel library has already been downloaded at least once.');
+ return;
+ }
+ var snippet_version = mixpanel_master['__SV'] || 0;
+ if (snippet_version < 1.1) {
+ // mixpanel wasn't initialized properly, report error and quit
+ console$1.critical('Version mismatch; please ensure you\'re using the latest version of the Mixpanel code snippet.');
+ return;
+ }
+
+ // Load instances of the Mixpanel Library
+ _.each(mixpanel_master['_i'], function(item) {
+ if (item && _.isArray(item)) {
+ instances[item[item.length-1]] = create_mplib.apply(this, item);
+ }
+ });
+
+ override_mp_init_func();
+ mixpanel_master['init']();
+
+ // Fire loaded events after updating the window's mixpanel object
+ _.each(instances, function(instance) {
+ instance._loaded();
+ });
+
+ add_dom_loaded_handler();
+ }
+
+ init_from_snippet();
+
+}());
diff --git a/web/webpack.config.js b/web/webpack.config.js
index bb23a500..7b73ecc4 100644
--- a/web/webpack.config.js
+++ b/web/webpack.config.js
@@ -6,7 +6,8 @@ const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: {
app: "./src/js/app.js",
- courseRedactor: "./src/js/course-redactor.js"
+ courseRedactor: "./src/js/course-redactor.js",
+ mixpanel: "./src/js/third_party/mixpanel-2-latest.js"
},
output: {
path: path.join(__dirname, "build"),
@@ -20,7 +21,7 @@ module.exports = {
loaders: [
{
test: /\.js$/,
- exclude: /(node_modules|bower_components)/,
+ exclude: /(node_modules|bower_components|third_party)/,
use: {
loader: 'babel-loader',
options: {
@@ -28,6 +29,16 @@ module.exports = {
}
}
},
+ {
+ test: /third_party\/.*\.js$/,
+ exclude: /(node_modules|bower_components)/,
+ use: {
+ loader: 'file-loader',
+ options: {
+ name: "[name].[ext]"
+ }
+ }
+ },
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
@@ -97,4 +108,4 @@ if (NODE_ENV === 'production') {
}
})
);
-}
\ No newline at end of file
+}
diff --git a/web/yarn.lock b/web/yarn.lock
index 0a35d84c..b8b74a0f 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -168,7 +168,7 @@ anymatch@^1.3.0:
micromatch "^2.1.5"
normalize-path "^2.0.0"
-aproba@^1.0.3:
+aproba@^1.0.3, aproba@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
@@ -260,7 +260,7 @@ arraybuffer.slice@~0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
-arrify@^1.0.0:
+arrify@^1.0.0, arrify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
@@ -961,7 +961,7 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
-bluebird@^3.0.5, bluebird@^3.1.1:
+bluebird@^3.0.5, bluebird@^3.1.1, bluebird@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
@@ -1139,6 +1139,10 @@ bs-recipes@1.3.4:
version "1.3.4"
resolved "https://registry.yarnpkg.com/bs-recipes/-/bs-recipes-1.3.4.tgz#0d2d4d48a718c8c044769fdc4f89592dc8b69585"
+buffer-from@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531"
+
buffer-xor@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
@@ -1165,6 +1169,24 @@ builtin-status-codes@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
+cacache@^10.0.4:
+ version "10.0.4"
+ resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460"
+ dependencies:
+ bluebird "^3.5.1"
+ chownr "^1.0.1"
+ glob "^7.1.2"
+ graceful-fs "^4.1.11"
+ lru-cache "^4.1.1"
+ mississippi "^2.0.0"
+ mkdirp "^0.5.1"
+ move-concurrently "^1.0.1"
+ promise-inflight "^1.0.1"
+ rimraf "^2.6.2"
+ ssri "^5.2.4"
+ unique-filename "^1.1.0"
+ y18n "^4.0.0"
+
cache-base@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
@@ -1323,6 +1345,10 @@ chokidar@1.7.0, chokidar@^1.7.0:
optionalDependencies:
fsevents "^1.0.0"
+chownr@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
+
cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@@ -1503,6 +1529,15 @@ concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+concat-stream@^1.5.0:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
+ dependencies:
+ buffer-from "^1.0.0"
+ inherits "^2.0.3"
+ readable-stream "^2.2.2"
+ typedarray "^0.0.6"
+
config-chain@~1.1.5:
version "1.1.11"
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.11.tgz#aba09747dfbe4c3e70e766a6e41586e1859fc6f2"
@@ -1562,10 +1597,34 @@ cookie@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
+copy-concurrently@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
+ dependencies:
+ aproba "^1.1.1"
+ fs-write-stream-atomic "^1.0.8"
+ iferr "^0.1.5"
+ mkdirp "^0.5.1"
+ rimraf "^2.5.4"
+ run-queue "^1.0.0"
+
copy-descriptor@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
+copy-webpack-plugin@^4.5.1:
+ version "4.5.1"
+ resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.5.1.tgz#fc4f68f4add837cc5e13d111b20715793225d29c"
+ dependencies:
+ cacache "^10.0.4"
+ find-cache-dir "^1.0.0"
+ globby "^7.1.1"
+ is-glob "^4.0.0"
+ loader-utils "^1.1.0"
+ minimatch "^3.0.4"
+ p-limit "^1.0.0"
+ serialize-javascript "^1.4.0"
+
core-js@^2.4.0, core-js@^2.5.0:
version "2.5.3"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
@@ -1770,6 +1829,10 @@ currently-unhandled@^0.4.1:
dependencies:
array-find-index "^1.0.1"
+cyclist@~0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
+
d@1:
version "1.0.0"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
@@ -1946,6 +2009,13 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
+dir-glob@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034"
+ dependencies:
+ arrify "^1.0.1"
+ path-type "^3.0.0"
+
doctypes@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9"
@@ -1999,6 +2069,15 @@ duplexer@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
+duplexify@^3.4.2, duplexify@^3.5.3:
+ version "3.5.4"
+ resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.4.tgz#4bb46c1796eabebeec4ca9a2e66b808cb7a3d8b4"
+ dependencies:
+ end-of-stream "^1.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+ stream-shift "^1.0.0"
+
duplexify@^3.5.0:
version "3.5.3"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.3.tgz#8b5818800df92fd0125b27ab896491912858243e"
@@ -2068,7 +2147,7 @@ encodeurl@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
-end-of-stream@^1.0.0:
+end-of-stream@^1.0.0, end-of-stream@^1.1.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
dependencies:
@@ -2495,6 +2574,13 @@ flatten@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
+flush-write-stream@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd"
+ dependencies:
+ inherits "^2.0.1"
+ readable-stream "^2.0.4"
+
follow-redirects@^1.2.5:
version "1.4.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.4.1.tgz#d8120f4518190f55aac65bb6fc7b85fcd666d6aa"
@@ -2559,6 +2645,13 @@ fresh@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+from2@^2.1.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
+ dependencies:
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+
from@~0:
version "0.1.7"
resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe"
@@ -2577,6 +2670,15 @@ fs-extra@3.0.1:
jsonfile "^3.0.0"
universalify "^0.1.0"
+fs-write-stream-atomic@^1.0.8:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
+ dependencies:
+ graceful-fs "^4.1.2"
+ iferr "^0.1.5"
+ imurmurhash "^0.1.4"
+ readable-stream "1 || 2"
+
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -2711,7 +2813,7 @@ glob@^5.0.12:
once "^1.3.0"
path-is-absolute "^1.0.0"
-glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@~7.1.1:
+glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2, glob@~7.1.1:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
dependencies:
@@ -2763,6 +2865,17 @@ globby@^5.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
+globby@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680"
+ dependencies:
+ array-union "^1.0.1"
+ dir-glob "^2.0.0"
+ glob "^7.1.2"
+ ignore "^3.3.5"
+ pify "^3.0.0"
+ slash "^1.0.0"
+
globule@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09"
@@ -2785,7 +2898,7 @@ glogg@^1.0.0:
dependencies:
sparkles "^1.0.0"
-graceful-fs@4.X, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
+graceful-fs@4.X, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
@@ -3294,6 +3407,14 @@ ieee754@^1.1.4:
version "1.1.8"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
+iferr@^0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
+
+ignore@^3.3.5:
+ version "3.3.7"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021"
+
ilyabirman-likely@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/ilyabirman-likely/-/ilyabirman-likely-2.3.0.tgz#4462becc5dedeb36b74bf4ba339a0ceab820785f"
@@ -3302,6 +3423,10 @@ immutable@3.8.2, immutable@^3.7.6:
version "3.8.2"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+
in-publish@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51"
@@ -3474,7 +3599,7 @@ is-extglob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
-is-extglob@^2.1.0:
+is-extglob@^2.1.0, is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -3506,6 +3631,12 @@ is-glob@^3.1.0:
dependencies:
is-extglob "^2.1.0"
+is-glob@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0"
+ dependencies:
+ is-extglob "^2.1.1"
+
is-number-like@^1.0.3:
version "1.0.8"
resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz#2e129620b50891042e44e9bbbb30593e75cfbbe3"
@@ -4411,6 +4542,21 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
+mississippi@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f"
+ dependencies:
+ concat-stream "^1.5.0"
+ duplexify "^3.4.2"
+ end-of-stream "^1.1.0"
+ flush-write-stream "^1.0.0"
+ from2 "^2.1.0"
+ parallel-transform "^1.1.0"
+ pump "^2.0.1"
+ pumpify "^1.3.3"
+ stream-each "^1.1.0"
+ through2 "^2.0.0"
+
mixin-deep@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.0.tgz#47a8732ba97799457c8c1eca28f95132d7e8150a"
@@ -4435,6 +4581,17 @@ moment@^2.20.1:
version "2.20.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd"
+move-concurrently@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
+ dependencies:
+ aproba "^1.1.1"
+ copy-concurrently "^1.0.0"
+ fs-write-stream-atomic "^1.0.8"
+ mkdirp "^0.5.1"
+ rimraf "^2.5.4"
+ run-queue "^1.0.3"
+
ms@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
@@ -4744,7 +4901,7 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
-once@^1.3.0, once@^1.3.3, once@^1.4.0:
+once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
dependencies:
@@ -4822,7 +4979,7 @@ p-finally@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
-p-limit@^1.1.0:
+p-limit@^1.0.0, p-limit@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c"
dependencies:
@@ -4842,6 +4999,14 @@ pako@~1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
+parallel-transform@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"
+ dependencies:
+ cyclist "~0.2.2"
+ inherits "^2.0.3"
+ readable-stream "^2.1.5"
+
parse-asn1@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712"
@@ -4953,6 +5118,12 @@ path-type@^2.0.0:
dependencies:
pify "^2.0.0"
+path-type@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
+ dependencies:
+ pify "^3.0.0"
+
pause-stream@0.0.11:
version "0.0.11"
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
@@ -5324,10 +5495,18 @@ process-nextick-args@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
+process-nextick-args@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
+
process@^0.11.10:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
+promise-inflight@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
+
promise@^7.0.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
@@ -5449,6 +5628,21 @@ pug-walk@^1.1.5:
pug-runtime "^2.0.3"
pug-strip-comments "^1.0.2"
+pump@^2.0.0, pump@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+pumpify@^1.3.3:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb"
+ dependencies:
+ duplexify "^3.5.3"
+ inherits "^2.0.3"
+ pump "^2.0.0"
+
punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
@@ -5551,6 +5745,18 @@ read-pkg@^2.0.0:
normalize-package-data "^2.3.2"
path-type "^2.0.0"
+"readable-stream@1 || 2", readable-stream@^2.0.4, readable-stream@^2.2.2:
+ version "2.3.5"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d"
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.0.3"
+ util-deprecate "~1.0.1"
+
"readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.17:
version "1.0.34"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
@@ -5811,7 +6017,7 @@ right-align@^0.1.1:
dependencies:
align-text "^0.1.1"
-rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1:
+rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
dependencies:
@@ -5824,6 +6030,12 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^2.0.0"
inherits "^2.0.1"
+run-queue@^1.0.0, run-queue@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
+ dependencies:
+ aproba "^1.1.1"
+
run-sequence@^1.1.5:
version "1.2.2"
resolved "https://registry.yarnpkg.com/run-sequence/-/run-sequence-1.2.2.tgz#5095a0bebe98733b0140bd08dd80ec030ddacdeb"
@@ -5916,6 +6128,10 @@ sequencify@~0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/sequencify/-/sequencify-0.0.7.tgz#90cff19d02e07027fd767f5ead3e7b95d1e7380c"
+serialize-javascript@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005"
+
serve-index@1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.8.0.tgz#7c5d96c13fb131101f93c1c5774f8516a1e78d3b"
@@ -6226,6 +6442,12 @@ sshpk@^1.7.0:
jsbn "~0.1.0"
tweetnacl "~0.14.0"
+ssri@^5.2.4:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.3.0.tgz#ba3872c9c6d33a0704a7d71ff045e5ec48999d06"
+ dependencies:
+ safe-buffer "^5.1.1"
+
static-extend@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@@ -6258,6 +6480,13 @@ stream-consume@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.0.tgz#a41ead1a6d6081ceb79f65b061901b6d8f3d1d0f"
+stream-each@^1.1.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.2.tgz#8e8c463f91da8991778765873fe4d960d8f616bd"
+ dependencies:
+ end-of-stream "^1.1.0"
+ stream-shift "^1.0.0"
+
stream-http@^2.7.2:
version "2.8.0"
resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.0.tgz#fd86546dac9b1c91aff8fc5d287b98fafb41bc10"
@@ -6563,6 +6792,10 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
+typedarray@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
+
ua-parser-js@0.7.12:
version "0.7.12"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb"
@@ -6623,6 +6856,18 @@ uniqs@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
+unique-filename@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.0.tgz#d05f2fe4032560871f30e93cbe735eea201514f3"
+ dependencies:
+ unique-slug "^2.0.0"
+
+unique-slug@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.0.tgz#db6676e7c7cc0629878ff196097c78855ae9f4ab"
+ dependencies:
+ imurmurhash "^0.1.4"
+
unique-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b"
@@ -6988,6 +7233,10 @@ y18n@^3.2.0, y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
+y18n@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
+
yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"