'use strict'; var Config = { DEBUG: false, LIB_VERSION: '2.32.0' }; // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file var window$1; if (typeof(window) === 'undefined') { var loc = { hostname: '' }; window$1 = { navigator: { userAgent: '' }, document: { location: loc, referrer: '' }, screen: { width: 0, height: 0 }, location: loc }; } else { window$1 = window; } /* * Saved references to long variable names, so that closure compiler can * minimize file size. */ var ArrayProto = Array.prototype; var ObjProto = Object.prototype; var slice = ArrayProto.slice; var toString = ObjProto.toString; var hasOwnProperty = ObjProto.hasOwnProperty; var windowConsole = window$1.console; var navigator$1 = window$1.navigator; var document$1 = window$1.document; var windowOpera = window$1.opera; var screen = window$1.screen; var userAgent = navigator$1.userAgent; var nativeForEach = ArrayProto.forEach; var nativeIndexOf = ArrayProto.indexOf; var nativeIsArray = Array.isArray; var breaker = {}; // Console override var console$1 = { /** @type {function(...*)} */ log: function() { if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) { try { windowConsole.log.apply(windowConsole, arguments); } catch (err) { _.each(arguments, function(arg) { windowConsole.log(arg); }); } } }, /** @type {function(...*)} */ error: function() { if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) { var args = ['Mixpanel error:'].concat(_.toArray(arguments)); try { windowConsole.error.apply(windowConsole, args); } catch (err) { _.each(args, function(arg) { windowConsole.error(arg); }); } } }, /** @type {function(...*)} */ critical: function() { if (!_.isUndefined(windowConsole) && windowConsole) { var args = ['Mixpanel error:'].concat(_.toArray(arguments)); try { windowConsole.error.apply(windowConsole, args); } catch (err) { _.each(args, function(arg) { windowConsole.error(arg); }); } } } }; // UNDERSCORE // Embed part of the Underscore Library var _ = { }; /** * @param {*=} obj * @param {function(...*)=} iterator * @param {Object=} context */ _.each = function(obj, iterator, context) { if (obj === null || obj === undefined) { return; } if (nativeForEach && obj.forEach === nativeForEach) { obj.forEach(iterator, context); } else if (obj.length === +obj.length) { for (var i = 0, l = obj.length; i < l; i++) { if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) { return; } } } else { for (var key in obj) { if (hasOwnProperty.call(obj, key)) { if (iterator.call(context, obj[key], key, obj) === breaker) { return; } } } } }; _.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; }; _.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 _.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]'; }; _.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; }; _.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]]); } }; _.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 }); }; })(); _.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 { var result = results[1]; try { result = decodeURIComponent(result); } catch(err) { console$1.error('Skipping decoding for malformed query param: ' + result); } return result.replace(/\+/g, ' '); } }; // _.cookie // Methods partially borrowed from quirksmode.org/js/cookies.html _.cookie = { get: function (name) { return }, parse: function (name) { return }, set_seconds: function(name, value, seconds, cross_subdomain, is_secure) { return }, set: function (name, value, days, cross_subdomain, is_secure) { return }, remove: function(name, cross_subdomain) { return } }; // _.localStorage _.localStorage = { is_supported: function () { return false }, error: function(msg) { return }, get: function(name) { return null; }, parse: function(name) { return null; }, set: function (name, value) { return }, remove: function(name) { return } }; _.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; }, /** * 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 (_.includes(user_agent, 'Edge')) { return 'Microsoft Edge'; } else if (_.includes(user_agent, 'Chrome')) { return 'Chrome'; } else if (_.includes(user_agent, 'CriOS')) { return 'Chrome iOS'; } 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, '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 = { 'Microsoft Edge': /Edge\/(\d+(\.\d+)?)/, 'Chrome': /Chrome\/(\d+(\.\d+)?)/, 'Chrome iOS': /CriOS\/(\d+(\.\d+)?)/, 'Safari': /Version\/(\d+(\.\d+)?)/, 'Mobile Safari': /Version\/(\d+(\.\d+)?)/, 'Firefox': /Firefox\/(\d+(\.\d+)?)/, 'Firefox iOS': /FxiOS\/(\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)) { return 'Windows'; } else if (/(iPhone|iPad|iPod)/.test(a)) { return 'iOS'; } else if (/Android/.test(a)) { return 'Android'; } else if (/(BlackBerry|PlayBook|BB10)/i.test(a)) { return 'BlackBerry'; } else if (/Mac/i.test(a)) { return 'Mac OS X'; } else if (/Linux/.test(a)) { return 'Linux'; } else if (/CrOS/.test(a)) { return 'Chrome OS'; } else { return ''; } }, device: function(user_agent) { if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { return 'Windows Phone'; } else if (/iPad/.test(user_agent)) { return 'iPad'; } else if (/iPod/.test(user_agent)) { return 'iPod Touch'; } else if (/iPhone/.test(user_agent)) { return 'iPhone'; } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { return 'BlackBerry'; } else if (/Android/.test(user_agent)) { return 'Android'; } else { return ''; } }, referringDomain: function(referrer) { var split = referrer.split('/'); if (split.length >= 3) { return split[2]; } return ''; }, properties: function() { return _.extend(_.strip_empty_properties({ '$os': _.info.os(), '$browser': _.info.browser(userAgent, navigator$1.vendor, windowOpera), '$referrer': document$1.referrer, '$referring_domain': _.info.referringDomain(document$1.referrer), '$device': _.info.device(userAgent) }), { '$current_url': window$1.location.href, '$browser_version': _.info.browserVersion(userAgent, navigator$1.vendor, windowOpera), '$screen_height': screen.height, '$screen_width': screen.width, 'mp_lib': 'web', '$lib_version': Config.LIB_VERSION, '$insert_id': Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10), 'time': Date.now() / 1000 // epoch time in seconds }); }, 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; _['isBlockedUA'] = _.isBlockedUA; _['isEmptyObject'] = _.isEmptyObject; _['info'] = _.info; _['info']['device'] = _.info.device; _['info']['browser'] = _.info.browser; _['info']['properties'] = _.info.properties; /** * A function used to track a Mixpanel event (e.g. MixpanelLib.track) * @callback trackFunction * @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. * @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. * @param {Function} [callback] If provided, the callback function will be called after tracking the event. */ /** Public **/ var GDPR_DEFAULT_PERSISTENCE_PREFIX = '__mp_opt_in_out_'; /** * Opt the user in to data tracking and cookies/localstorage for the given token * @param {string} token - Mixpanel project tracking token * @param {Object} [options] * @param {trackFunction} [options.track] - function used for tracking a Mixpanel event to record the opt-in action * @param {string} [options.trackEventName] - event name to be used for tracking the opt-in action * @param {Object} [options.trackProperties] - set of properties to be tracked along with the opt-in action * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name * @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires * @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not * @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not */ function optIn(token, options) { _optInOut(true, token, options); } /** * Opt the user out of data tracking and cookies/localstorage for the given token * @param {string} token - Mixpanel project tracking token * @param {Object} [options] * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name * @param {Number} [options.cookieExpiration] - number of days until the opt-out cookie expires * @param {boolean} [options.crossSubdomainCookie] - whether the opt-out cookie is set as cross-subdomain or not * @param {boolean} [options.secureCookie] - whether the opt-out cookie is set as secure or not */ function optOut(token, options) { _optInOut(false, token, options); } /** * Check whether the user has opted in to data tracking and cookies/localstorage for the given token * @param {string} token - Mixpanel project tracking token * @param {Object} [options] * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name * @returns {boolean} whether the user has opted in to the given opt type */ function hasOptedIn(token, options) { return _getStorageValue(token, options) === '1'; } /** * Check whether the user has opted out of data tracking and cookies/localstorage for the given token * @param {string} token - Mixpanel project tracking token * @param {Object} [options] * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name * @returns {boolean} whether the user has opted out of the given opt type */ function hasOptedOut(token, options) { if (_hasDoNotTrackFlagOn(options)) { return true; } return _getStorageValue(token, options) === '0'; } /** * Wrap a MixpanelLib method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token * If the user has opted out, return early instead of executing the method. * If a callback argument was provided, execute it passing the 0 error code. * @param {function} method - wrapped method to be executed if the user has not opted out * @returns {*} the result of executing method OR undefined if the user has opted out */ function addOptOutCheckMixpanelLib(method) { return _addOptOutCheck(method, function(name) { return this.get_config(name); }); } /** * Wrap a MixpanelPeople method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token * If the user has opted out, return early instead of executing the method. * If a callback argument was provided, execute it passing the 0 error code. * @param {function} method - wrapped method to be executed if the user has not opted out * @returns {*} the result of executing method OR undefined if the user has opted out */ function addOptOutCheckMixpanelPeople(method) { return _addOptOutCheck(method, function(name) { return this._get_config(name); }); } /** * Clear the user's opt in/out status of data tracking and cookies/localstorage for the given token * @param {string} token - Mixpanel project tracking token * @param {Object} [options] * @param {string} [options.persistenceType] Persistence mechanism used - cookie or localStorage * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name * @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires * @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not * @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not */ function clearOptInOut(token, options) { options = options || {}; _getStorage(options).remove(_getStorageKey(token, options), !!options.crossSubdomainCookie); } /** Private **/ /** * Get storage util * @param {Object} [options] * @param {string} [options.persistenceType] * @returns {object} either _.cookie or _.localstorage */ function _getStorage(options) { options = options || {}; return options.persistenceType === 'localStorage' ? _.localStorage : _.cookie; } /** * Get the name of the cookie that is used for the given opt type (tracking, cookie, etc.) * @param {string} token - Mixpanel project tracking token * @param {Object} [options] * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name * @returns {string} the name of the cookie for the given opt type */ function _getStorageKey(token, options) { options = options || {}; return (options.persistencePrefix || GDPR_DEFAULT_PERSISTENCE_PREFIX) + token; } /** * Get the value of the cookie that is used for the given opt type (tracking, cookie, etc.) * @param {string} token - Mixpanel project tracking token * @param {Object} [options] * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name * @returns {string} the value of the cookie for the given opt type */ function _getStorageValue(token, options) { return _getStorage(options).get(_getStorageKey(token, options)); } /** * Check whether the user has set the DNT/doNotTrack setting to true in their browser * @param {Object} [options] * @param {string} [options.window] - alternate window object to check; used to force various DNT settings in browser tests * @returns {boolean} whether the DNT setting is true */ function _hasDoNotTrackFlagOn(options) { var win = (options && options.window) || window$1; var nav = win['navigator'] || {}; var hasDntOn = false; _.each([ nav['doNotTrack'], // standard nav['msDoNotTrack'], win['doNotTrack'] ], function(dntValue) { if (_.includes([true, 1, '1', 'yes'], dntValue)) { hasDntOn = true; } }); return hasDntOn; } /** * Set cookie/localstorage for the user indicating that they are opted in or out for the given opt type * @param {boolean} optValue - whether to opt the user in or out for the given opt type * @param {string} token - Mixpanel project tracking token * @param {Object} [options] * @param {trackFunction} [options.track] - function used for tracking a Mixpanel event to record the opt-in action * @param {string} [options.trackEventName] - event name to be used for tracking the opt-in action * @param {Object} [options.trackProperties] - set of properties to be tracked along with the opt-in action * @param {string} [options.persistencePrefix=__mp_opt_in_out] - custom prefix to be used in the cookie/localstorage name * @param {Number} [options.cookieExpiration] - number of days until the opt-in cookie expires * @param {boolean} [options.crossSubdomainCookie] - whether the opt-in cookie is set as cross-subdomain or not * @param {boolean} [options.secureCookie] - whether the opt-in cookie is set as secure or not */ function _optInOut(optValue, token, options) { if (!_.isString(token) || !token.length) { console.error('gdpr.' + (optValue ? 'optIn' : 'optOut') + ' called with an invalid token'); return; } options = options || {}; _getStorage(options).set( _getStorageKey(token, options), optValue ? 1 : 0, _.isNumber(options.cookieExpiration) ? options.cookieExpiration : null, !!options.crossSubdomainCookie, !!options.secureCookie ); if (options.track && optValue) { // only track event if opting in (optValue=true) options.track(options.trackEventName || '$opt_in', options.trackProperties); } } /** * Wrap a method with a check for whether the user is opted out of data tracking and cookies/localstorage for the given token * If the user has opted out, return early instead of executing the method. * If a callback argument was provided, execute it passing the 0 error code. * @param {function} method - wrapped method to be executed if the user has not opted out * @param {function} getConfigValue - getter function for the Mixpanel API token and other options to be used with opt-out check * @returns {*} the result of executing method OR undefined if the user has opted out */ function _addOptOutCheck(method, getConfigValue) { return function() { var optedOut = false; try { var token = getConfigValue.call(this, 'token'); var persistenceType = getConfigValue.call(this, 'opt_out_tracking_persistence_type'); var persistencePrefix = getConfigValue.call(this, 'opt_out_tracking_cookie_prefix'); var win = getConfigValue.call(this, 'window'); // used to override window during browser tests if (token) { // if there was an issue getting the token, continue method execution as normal optedOut = hasOptedOut(token, { persistenceType: persistenceType, persistencePrefix: persistencePrefix, window: win }); } } catch(err) { console.error('Unexpected error when checking tracking opt-out status: ' + err); } if (!optedOut) { return method.apply(this, arguments); } var callback = arguments[arguments.length - 1]; if (typeof(callback) === 'function') { callback(0); } return; }; } /** @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'; /** @const */ var REMOVE_ACTION = '$remove'; /** @const */ var DELETE_ACTION = '$delete'; // Common internal methods for mixpanel.people and mixpanel.group APIs. // These methods shouldn't involve network I/O. var apiActions = { set_action: function(prop, to) { var data = {}; var $set = {}; if (_.isObject(prop)) { _.each(prop, function(v, k) { if (!this._is_reserved_property(k)) { $set[k] = v; } }, this); } else { $set[prop] = to; } data[SET_ACTION] = $set; return data; }, unset_action: function(prop) { 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 data; }, set_once_action: function(prop, to) { var data = {}; var $set_once = {}; if (_.isObject(prop)) { _.each(prop, function(v, k) { if (!this._is_reserved_property(k)) { $set_once[k] = v; } }, this); } else { $set_once[prop] = to; } data[SET_ONCE_ACTION] = $set_once; return data; }, union_action: function(list_name, values) { 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); } else { $union[list_name] = _.isArray(values) ? values : [values]; } data[UNION_ACTION] = $union; return data; }, append_action: function(list_name, value) { var data = {}; var $append = {}; if (_.isObject(list_name)) { _.each(list_name, function(v, k) { if (!this._is_reserved_property(k)) { $append[k] = v; } }, this); } else { $append[list_name] = value; } data[APPEND_ACTION] = $append; return data; }, remove_action: function(list_name, value) { var data = {}; var $remove = {}; if (_.isObject(list_name)) { _.each(list_name, function(v, k) { if (!this._is_reserved_property(k)) { $remove[k] = v; } }, this); } else { $remove[list_name] = value; } data[REMOVE_ACTION] = $remove; return data; }, delete_action: function() { var data = {}; data[DELETE_ACTION] = ''; return data; } }; /* * Constants */ /** @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 REMOVE_QUEUE_KEY = '__mpr'; /** @const */ var UNION_QUEUE_KEY = '__mpu'; // 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, REMOVE_QUEUE_KEY, UNION_QUEUE_KEY, PEOPLE_DISTINCT_ID_KEY, ALIAS_ID_KEY, CAMPAIGN_IDS_KEY, EVENT_TIMERS_KEY ]; /** * Mixpanel Persistence Object * @constructor */ var MixpanelPersistence = function(config) { this['props'] = {}; this.campaign_params_saved = false; if (config['persistence_name']) { this.name = 'mp_' + config['persistence_name']; } else { this.name = 'mp_' + config['token'] + '_mixpanel'; } var storage_type = config['persistence']; if (storage_type !== 'cookie' && storage_type !== 'localStorage') { console$1.critical('Unknown persistence type ' + storage_type + '; falling back to cookie'); storage_type = config['persistence'] = 'cookie'; } if (storage_type === 'localStorage' && _.localStorage.is_supported()) { this.storage = _.localStorage; } else { this.storage = _.cookie; } this.load(); this.update_config(config); this.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.save = function() { if (this.disabled) { return; } this.storage.set( this.name, 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(); } }; // safely fills the passed in object with stored properties, // does not override any properties defined in both // returns the passed in object MixpanelPersistence.prototype.safe_merge = function(props) { _.each(this['props'], function(val, prop) { if (!(prop in props)) { props[prop] = val; } }); return props; }; MixpanelPersistence.prototype.update_config = function(config) { this.default_expiry = this.expire_days = config['cookie_expiration']; this.set_disabled(config['disable_persistence']); this.set_cross_subdomain(config['cross_subdomain_cookie']); this.set_secure(config['secure_cookie']); }; MixpanelPersistence.prototype.set_disabled = function(disabled) { this.disabled = disabled; if (this.disabled) { this.remove(); } else { this.save(); } }; MixpanelPersistence.prototype.set_cross_subdomain = function(cross_subdomain) { if (cross_subdomain !== this.cross_subdomain) { this.cross_subdomain = cross_subdomain; this.remove(); this.save(); } }; MixpanelPersistence.prototype.get_cross_subdomain = function() { return this.cross_subdomain; }; MixpanelPersistence.prototype.set_secure = function(secure) { if (secure !== this.secure) { this.secure = secure ? true : false; this.remove(); this.save(); } }; MixpanelPersistence.prototype._add_to_people_queue = function(queue, data) { var q_key = this._get_queue_key(queue), q_data = data[queue], set_q = this._get_or_create_queue(SET_ACTION), set_once_q = this._get_or_create_queue(SET_ONCE_ACTION), unset_q = this._get_or_create_queue(UNSET_ACTION), add_q = this._get_or_create_queue(ADD_ACTION), union_q = this._get_or_create_queue(UNION_ACTION), remove_q = this._get_or_create_queue(REMOVE_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 === REMOVE_QUEUE_KEY) { remove_q.push(q_data); this._pop_from_people_queue(APPEND_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) { if (queue === APPEND_ACTION || queue === REMOVE_ACTION) { // list actions: only remove if both k+v match // e.g. remove should not override append in a case like // append({foo: 'bar'}); remove({foo: 'qux'}) _.each(q, function(queued_action) { if (queued_action[k] === v) { delete queued_action[k]; } }); } else { 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 === REMOVE_ACTION) { return REMOVE_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); }; /* * This file is a js implementation for a subset in eval_node.c */ /* * Constants */ // Metadata keys /** @const */ var OPERATOR_KEY = 'operator'; /** @const */ var PROPERTY_KEY = 'property'; /** @const */ var WINDOW_KEY = 'window'; /** @const */ var UNIT_KEY = 'unit'; /** @const */ var VALUE_KEY = 'value'; /** @const */ var HOUR_KEY = 'hour'; /** @const */ var DAY_KEY = 'day'; /** @const */ var WEEK_KEY = 'week'; /** @const */ var MONTH_KEY = 'month'; // Operands /** @const */ var EVENT_PROPERTY = 'event'; /** @const */ var LITERAL_PROPERTY = 'literal'; // Binary Operators /** @const */ var AND_OPERATOR = 'and'; /** @const */ var OR_OPERATOR = 'or'; /** @const */ var IN_OPERATOR = 'in'; /** @const */ var NOT_IN_OPERATOR = 'not in'; /** @const */ var PLUS_OPERATOR = '+'; /** @const */ var MINUS_OPERATOR = '-'; /** @const */ var MUL_OPERATOR = '*'; /** @const */ var DIV_OPERATOR = '/'; /** @const */ var MOD_OPERATOR = '%'; /** @const */ var EQUALS_OPERATOR = '=='; /** @const */ var NOT_EQUALS_OPERATOR = '!='; /** @const */ var GREATER_OPERATOR = '>'; /** @const */ var LESS_OPERATOR = '<'; /** @const */ var GREATER_EQUAL_OPERATOR = '>='; /** @const */ var LESS_EQUAL_OPERATOR = '<='; // Typecast Operators /** @const */ var BOOLEAN_OPERATOR = 'boolean'; /** @const */ var DATETIME_OPERATOR = 'datetime'; /** @const */ var LIST_OPERATOR = 'list'; /** @const */ var NUMBER_OPERATOR = 'number'; /** @const */ var STRING_OPERATOR = 'string'; // Unary Operators /** @const */ var NOT_OPERATOR = 'not'; /** @const */ var DEFINED_OPERATOR = 'defined'; /** @const */ var NOT_DEFINED_OPERATOR = 'not defined'; // Special literals /** @const */ var NOW_LITERAL = 'now'; // Type cast functions function toNumber(value) { if (value === null) { return null; } switch (typeof(value)) { case 'object': if (_.isDate(value) && value.getTime() >= 0) { return value.getTime(); } return null; case 'boolean': return Number(value); case 'number': return value; case 'string': value = Number(value); if (!isNaN(value)) { return value; } return 0; } return null; } function evaluateNumber(op, properties) { if (!op['operator'] || op['operator'] !== NUMBER_OPERATOR || !op['children'] || op['children'].length !== 1) { throw ('Invalid cast operator: number ' + op); } return toNumber(evaluateSelector(op['children'][0], properties)); } function toBoolean(value) { if (value === null) { return false; } switch (typeof value) { case 'boolean': return value; case 'number': return value !== 0.0; case 'string': return value.length > 0; case 'object': if (_.isArray(value) && value.length > 0) { return true; } if (_.isDate(value) && value.getTime() > 0) { return true; } if (_.isObject(value) && !_.isEmptyObject(value)) { return true; } return false; } return false; } function evaluateBoolean(op, properties) { if (!op['operator'] || op['operator'] !== BOOLEAN_OPERATOR || !op['children'] || op['children'].length !== 1) { throw ('Invalid cast operator: boolean ' + op); } return toBoolean(evaluateSelector(op['children'][0], properties)); } function evaluateDateTime(op, properties) { if (!op['operator'] || op['operator'] !== DATETIME_OPERATOR || !op['children'] || op['children'].length !== 1) { throw ('Invalid cast operator: datetime ' + op); } var v = evaluateSelector(op['children'][0], properties); if (v === null) { return null; } switch (typeof(v)) { case 'number': case 'string': var d = new Date(v); if (isNaN(d.getTime())) { return null; } return d; case 'object': if (_.isDate(v)) { return v; } } return null; } function evaluateList(op, properties) { if (!op['operator'] || op['operator'] !== LIST_OPERATOR || !op['children'] || op['children'].length !== 1) { throw ('Invalid cast operator: list ' + op); } var v = evaluateSelector(op['children'][0], properties); if (v === null) { return null; } if (_.isArray(v)) { return v; } return null; } function evaluateString(op, properties) { if (!op['operator'] || op['operator'] !== STRING_OPERATOR || !op['children'] || op['children'].length !== 1) { throw ('Invalid cast operator: string ' + op); } var v = evaluateSelector(op['children'][0], properties); switch (typeof(v)) { case 'object': if (_.isDate(v)) { return v.toJSON(); } return JSON.stringify(v); } return String(v); } // Operators function evaluateAnd(op, properties) { if (!op['operator'] || op['operator'] !== AND_OPERATOR || !op['children'] || op['children'].length !== 2) { throw ('Invalid operator: AND ' + op); } return toBoolean(evaluateSelector(op['children'][0], properties)) && toBoolean(evaluateSelector(op['children'][1], properties)); } function evaluateOr(op, properties) { if (!op['operator'] || op['operator'] !== OR_OPERATOR || !op['children'] || op['children'].length !== 2) { throw ('Invalid operator: OR ' + op); } return toBoolean(evaluateSelector(op['children'][0], properties)) || toBoolean(evaluateSelector(op['children'][1], properties)); } function evaluateIn(op, properties) { if (!op['operator'] || [IN_OPERATOR, NOT_IN_OPERATOR].indexOf(op['operator']) === -1 || !op['children'] || op['children'].length !== 2) { throw ('Invalid operator: IN/NOT IN ' + op); } var leftValue = evaluateSelector(op['children'][0], properties); var rightValue = evaluateSelector(op['children'][1], properties); if (!_.isArray(rightValue) && !_.isString(rightValue)) { throw ('Invalid operand for operator IN: invalid type' + rightValue); } var v = rightValue.indexOf(leftValue) > -1; if (op['operator'] === NOT_IN_OPERATOR) { return !v; } return v; } function evaluatePlus(op, properties) { if (!op['operator'] || op['operator'] !== PLUS_OPERATOR || !op['children'] || op['children'].length < 2) { throw ('Invalid operator: PLUS ' + op); } var l = evaluateSelector(op['children'][0], properties); var r = evaluateSelector(op['children'][1], properties); if (typeof l === 'number' && typeof r === 'number') { return l + r; } if (typeof l === 'string' && typeof r === 'string') { return l + r; } return null; } function evaluateArithmetic(op, properties) { if (!op['operator'] || [MINUS_OPERATOR, MUL_OPERATOR, DIV_OPERATOR, MOD_OPERATOR].indexOf(op['operator']) === -1 || !op['children'] || op['children'].length < 2) { throw ('Invalid arithmetic operator ' + op); } var l = evaluateSelector(op['children'][0], properties); var r = evaluateSelector(op['children'][1], properties); if (typeof l === 'number' && typeof r === 'number') { switch (op['operator']) { case MINUS_OPERATOR: return l - r; case MUL_OPERATOR: return l * r; case DIV_OPERATOR: if (r !== 0) { return l / r; } return null; case MOD_OPERATOR: if (r === 0) { return null; } if (l === 0) { return 0; } if ((l < 0 && r > 0) || (l > 0 && r < 0)) { /* Mimic python modulo - result takes sign of the divisor * if one operand is negative. */ return -(Math.floor(l / r) * r - l); } return l % r; default: throw('Unknown operator: ' + op['operator']); } } return null; } function _isArrayEqual(l, r) { if (l === r) return true; if (l === null || r === null) return false; if (l.length !== r.length) return false; for (var i = 0; i < l.length; i++) { if (l[i] !== r[i]) { return false; } } return true; } function _isEqual(l, r) { if ( l === null && l === r ) { return true; } if (typeof l === typeof r) { switch (typeof l) { case 'number': case 'string': case 'boolean': return l === r; case 'object': if (_.isArray(l) && _.isArray(r)) { return _isArrayEqual(l, r); } if (_.isDate(l) && _.isDate(r)) { return l.getTime() === r.getTime(); } if (_.isObject(l) && _.isObject(r)) { return JSON.stringify(l) === JSON.stringify(r); } } } return false; } function evaluateEquality(op, properties) { if (!op['operator'] || [EQUALS_OPERATOR, NOT_EQUALS_OPERATOR].indexOf(op['operator']) === -1 || !op['children'] || op['children'].length !== 2) { throw ('Invalid equality operator ' + op); } var v = _isEqual(evaluateSelector(op['children'][0], properties), evaluateSelector(op['children'][1], properties)); switch (op['operator']) { case EQUALS_OPERATOR: return v; case NOT_EQUALS_OPERATOR: return !v; } } function evaluateComparison(op, properties) { if (!op['operator'] || [GREATER_OPERATOR, GREATER_EQUAL_OPERATOR, LESS_OPERATOR, LESS_EQUAL_OPERATOR].indexOf(op['operator']) === -1 || !op['children'] || op['children'].length !== 2) { throw ('Invalid comparison operator ' + op); } var l = evaluateSelector(op['children'][0], properties); var r = evaluateSelector(op['children'][1], properties); if (typeof(l) === typeof(r)) { if (typeof(r) === 'number' || _.isDate(r)) { l = toNumber(l); r = toNumber(r); switch (op['operator']) { case GREATER_OPERATOR: return l > r; case GREATER_EQUAL_OPERATOR: return l >= r; case LESS_OPERATOR: return l < r; case LESS_EQUAL_OPERATOR: return l <= r; } } else if (typeof(r) === 'string') { var compare = l.localeCompare(r); switch (op['operator']) { case GREATER_OPERATOR: return compare > 0; case GREATER_EQUAL_OPERATOR: return compare >= 0; case LESS_OPERATOR: return compare < 0; case LESS_EQUAL_OPERATOR: return compare <= 0; } } } return null; } function evaluateDefined(op, properties) { if (!op['operator'] || [DEFINED_OPERATOR, NOT_DEFINED_OPERATOR].indexOf(op['operator']) === -1 || !op['children'] || op['children'].length !== 1) { throw ('Invalid defined/not defined operator: ' + op); } var b = evaluateSelector(op['children'][0], properties) !== null; if (op['operator'] === NOT_DEFINED_OPERATOR) { return !b; } return b; } function evaluateNot(op, properties) { if (!op['operator'] || op['operator'] !== NOT_OPERATOR || !op['children'] || op['children'].length !== 1) { throw ('Invalid not operator: ' + op); } var v = evaluateSelector(op['children'][0], properties); if (v === null) { return true; } if (typeof(v) === 'boolean') { return !v; } return null; } function evaluateOperator(op, properties) { if (!op['operator']) { throw ('Invalid operator: operator key missing ' + op); } switch (op['operator']) { case AND_OPERATOR: return evaluateAnd(op, properties); case OR_OPERATOR: return evaluateOr(op, properties); case IN_OPERATOR: case NOT_IN_OPERATOR: return evaluateIn(op, properties); case PLUS_OPERATOR: return evaluatePlus(op, properties); case MINUS_OPERATOR: case MUL_OPERATOR: case DIV_OPERATOR: case MOD_OPERATOR: return evaluateArithmetic(op, properties); case EQUALS_OPERATOR: case NOT_EQUALS_OPERATOR: return evaluateEquality(op, properties); case GREATER_OPERATOR: case LESS_OPERATOR: case GREATER_EQUAL_OPERATOR: case LESS_EQUAL_OPERATOR: return evaluateComparison(op, properties); case BOOLEAN_OPERATOR: return evaluateBoolean(op, properties); case DATETIME_OPERATOR: return evaluateDateTime(op, properties); case LIST_OPERATOR: return evaluateList(op, properties); case NUMBER_OPERATOR: return evaluateNumber(op, properties); case STRING_OPERATOR: return evaluateString(op, properties); case DEFINED_OPERATOR: case NOT_DEFINED_OPERATOR: return evaluateDefined(op, properties); case NOT_OPERATOR: return evaluateNot(op, properties); } } function evaluateWindow(value) { var win = value[WINDOW_KEY]; if (!win || !win[UNIT_KEY] || !win[VALUE_KEY]) { throw('Invalid window: missing required keys ' + JSON.stringify(value)); } var out = new Date(); switch (win[UNIT_KEY]) { case HOUR_KEY: out.setTime(out.getTime() + (win[VALUE_KEY]*-1*60*60*1000)); break; case DAY_KEY: out.setTime(out.getTime() + (win[VALUE_KEY]*-1*24*60*60*1000)); break; case WEEK_KEY: out.setTime(out.getTime() + (win[VALUE_KEY]*-1*7*24*60*60*1000)); break; case MONTH_KEY: out.setTime(out.getTime() + (win[VALUE_KEY]*-1*30*24*60*60*1000)); break; default: throw('Invalid unit: ' + win[UNIT_KEY]); } return out; } function evaluateOperand(op, properties) { if (!op['property'] || !op['value']) { throw('Invalid operand: missing required keys ' + op); } switch (op['property']) { case EVENT_PROPERTY: if (properties[op['value']] !== undefined) { return properties[op['value']]; } return null; case LITERAL_PROPERTY: if (op['value'] === NOW_LITERAL) { return new Date(); } if (typeof(op['value']) === 'object') { return evaluateWindow(op['value']); } return op['value']; default: throw('Invalid operand: Invalid property type ' + op['property']); } } function evaluateSelector(filters, properties) { if (filters[PROPERTY_KEY]) { return evaluateOperand(filters, properties); } if (filters[OPERATOR_KEY]) { return evaluateOperator(filters, properties); } } /** * Mixpanel People Object * @constructor */ var MixpanelPeople = function() {}; _.extend(MixpanelPeople.prototype, apiActions); 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 tracking the event. */ MixpanelPeople.prototype.set = addOptOutCheckMixpanelPeople(function(prop, to, callback) { var data = this.set_action(prop, to); if (_.isObject(prop)) { callback = to; } // update $set object with default people properties data[SET_ACTION] = _.extend( {}, _.info.people_properties(), data[SET_ACTION] ); 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 tracking the event. */ MixpanelPeople.prototype.set_once = addOptOutCheckMixpanelPeople(function(prop, to, callback) { var data = this.set_once_action(prop, to); if (_.isObject(prop)) { callback = to; } 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 tracking the event. */ MixpanelPeople.prototype.unset = addOptOutCheckMixpanelPeople(function(prop, callback) { var data = this.unset_action(prop); 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 tracking the event. */ MixpanelPeople.prototype.increment = addOptOutCheckMixpanelPeople(function(prop, by, callback) { var data = {}; var $add = {}; if (_.isObject(prop)) { _.each(prop, function(v, k) { if (!this._is_reserved_property(k)) { if (isNaN(parseFloat(v))) { console$1.error('Invalid increment value passed to mixpanel.people.increment - must be a number'); return; } else { $add[k] = v; } } }, this); callback = by; } else { // convenience: mixpanel.people.increment('property'); will // increment 'property' by 1 if (_.isUndefined(by)) { by = 1; } $add[prop] = by; } data[ADD_ACTION] = $add; return this._send_request(data, callback); }); /* * Append a value to a list-valued people analytics property. * * ### Usage: * * // append a value to a list, creating it if needed * mixpanel.people.append('pages_visited', 'homepage'); * * // like mixpanel.people.set(), you can append multiple * // properties at once: * mixpanel.people.append({ * list1: 'bob', * list2: 123 * }); * * @param {Object|String} list_name 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 An item to append to the list * @param {Function} [callback] If provided, the callback will be called after tracking the event. */ MixpanelPeople.prototype.append = addOptOutCheckMixpanelPeople(function(list_name, value, callback) { if (_.isObject(list_name)) { callback = value; } var data = this.append_action(list_name, value); return this._send_request(data, callback); }); /* * Remove a value from a list-valued people analytics property. * * ### Usage: * * mixpanel.people.remove('School', 'UCB'); * * @param {Object|String} list_name 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 Item to remove from the list * @param {Function} [callback] If provided, the callback will be called after tracking the event. */ MixpanelPeople.prototype.remove = addOptOutCheckMixpanelPeople(function(list_name, value, callback) { if (_.isObject(list_name)) { callback = value; } var data = this.remove_action(list_name, value); 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} list_name 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 tracking the event. */ MixpanelPeople.prototype.union = addOptOutCheckMixpanelPeople(function(list_name, values, callback) { if (_.isObject(list_name)) { callback = values; } var data = this.union_action(list_name, values); return this._send_request(data, callback); }); /* * Record that you have charged the current user a certain amount * of money. Charges recorded with track_charge() will appear in the * Mixpanel revenue report. * * ### Usage: * * // charge a user $50 * mixpanel.people.track_charge(50); * * // charge a user $30.50 on the 2nd of january * mixpanel.people.track_charge(30.50, { * '$time': new Date('jan 1 2012') * }); * * @param {Number} amount The amount of money charged to the current user * @param {Object} [properties] An associative array of properties associated with the charge * @param {Function} [callback] If provided, the callback will be called when the server responds */ MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function(amount, properties, callback) { if (!_.isNumber(amount)) { amount = parseFloat(amount); if (isNaN(amount)) { console$1.error('Invalid value passed to mixpanel.people.track_charge - must be a number'); return; } } return this.append('$transactions', _.extend({ '$amount': amount }, properties), callback); }); /* * Permanently clear all revenue report transactions from the * current user's people analytics profile. * * ### Usage: * * mixpanel.people.clear_charges(); * * @param {Function} [callback] If provided, the callback will be called after tracking the 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 device_id = this._mixpanel.get_property('$device_id'); var user_id = this._mixpanel.get_property('$user_id'); var had_persisted_distinct_id = this._mixpanel.get_property('$had_persisted_distinct_id'); if (device_id) { data['$device_id'] = device_id; } if (user_id) { data['$user_id'] = user_id; } if (had_persisted_distinct_id) { data['$had_persisted_distinct_id'] = had_persisted_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 (REMOVE_ACTION in data) { this._mixpanel['persistence']._add_to_people_queue(REMOVE_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, _remove_callback ) { var _this = this; var $append_queue = this._mixpanel['persistence']._get_queue(APPEND_ACTION); var $remove_queue = this._mixpanel['persistence']._get_queue(REMOVE_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 append_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(); if (!_.isEmptyObject($append_item)) { _this.append($append_item, append_callback); } } // Save the shortened append queue _this._mixpanel['persistence'].save(); } // same for $remove if (!_.isUndefined($remove_queue) && _.isArray($remove_queue) && $remove_queue.length) { var $remove_item; var remove_callback = function(response, data) { if (response === 0) { _this._mixpanel['persistence']._add_to_people_queue(REMOVE_ACTION, $remove_item); } if (!_.isUndefined(_remove_callback)) { _remove_callback(response, data); } }; for (var j = $remove_queue.length - 1; j >= 0; j--) { $remove_item = $remove_queue.pop(); if (!_.isEmptyObject($remove_item)) { _this.remove($remove_item, remove_callback); } } _this._mixpanel['persistence'].save(); } }; MixpanelPeople.prototype._is_reserved_property = function(prop) { return prop === '$distinct_id' || prop === '$token' || prop === '$device_id' || prop === '$user_id' || prop === '$had_persisted_distinct_id'; }; // 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['remove'] = MixpanelPeople.prototype.remove; 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; /* * 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; /** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel'; /* * Dynamic... constants? Is that an oxymoron? */ // http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/ // https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#withCredentials var USE_XHR = (window$1.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()); // IE<10 does not support cross-origin XHR's but script tags // with defer won't block window.onload; ENQUEUE_REQUESTS // should only be true for Opera<12 var ENQUEUE_REQUESTS = !USE_XHR && (userAgent.indexOf('MSIE') === -1) && (userAgent.indexOf('Mozilla') === -1); /* * Module-level globals */ var DEFAULT_CONFIG = { 'api_host': 'https://api-js.mixpanel.com', 'api_method': 'POST', 'cross_subdomain_cookie': true, 'persistence': 'cookie', 'persistence_name': '', 'cookie_name': '', 'loaded': function() {}, 'test': false, 'verbose': false, 'img': false, 'track_pageview': true, 'debug': false, 'cookie_expiration': 365, 'disable_persistence': false, 'disable_cookie': false, 'secure_cookie': false, 'ip': true, 'opt_out_tracking_by_default': false, 'opt_out_persistence_by_default': false, 'opt_out_tracking_persistence_type': 'localStorage', 'opt_out_tracking_cookie_prefix': null, 'property_blacklist': [], 'xhr_headers': {} // { header: value, header2: value } }; var DOM_LOADED = false; /** * Mixpanel Library Object * @constructor */ var MixpanelLib = function() {}; /** * 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'); // 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._gdpr_init(); var uuid = _.UUID(); if (!this.get_distinct_id()) { // There is no need to set the distinct id // or the device id if something was already stored // in the persitence this.register_once({ 'distinct_id': uuid, '$device_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); if (!this.has_opted_out_tracking()) { _.each(this.__request_queue, function(item) { this._send_request.apply(this, item); }, this); } delete this.__dom_loaded_queue; delete this.__request_queue; }; MixpanelLib.prototype._track_dom = function(DomClass, args) { if (this.get_config('img')) { console$1.error('You can\'t use DOM tracking functions with img = true.'); return false; } if (!DOM_LOADED) { this.__dom_loaded_queue.push([DomClass, args]); return false; } var dt = new DomClass().init(this); return dt.track.apply(dt, args); }; /** * _prepare_callback() should be called by callers of _send_request for use * as the callback argument. * * If there is no callback, this returns null. * If we are going to make XHR/XDR requests, this returns a function. * If we are going to use script tags, this returns a string to use as the * callback GET param. */ MixpanelLib.prototype._prepare_callback = function(callback, data) { if (_.isUndefined(callback)) { return null; } if (USE_XHR) { var callback_function = function(response) { callback(response, data); }; return callback_function; } else { // if the user gives us a callback, we store as a random // property on this instances jsc function and update our // callback string to reflect that. var jsc = this['_jsc']; var randomized_cb = '' + Math.floor(Math.random() * 100000000); var callback_string = this.get_config('callback_fn') + '[' + randomized_cb + ']'; jsc[randomized_cb] = function(response) { delete jsc[randomized_cb]; callback(response, data); }; return callback_string; } }; MixpanelLib.prototype._send_request = function(url, data, options, callback) { if (ENQUEUE_REQUESTS) { this.__request_queue.push(arguments); return; } var DEFAULT_OPTIONS = {method: this.get_config('api_method')}; var body_data = null; if (!callback && (_.isFunction(options) || typeof options === 'string')) { callback = options; options = null; } options = _.extend(DEFAULT_OPTIONS, options || {}); if (!USE_XHR) { options.method = 'GET'; } var use_post = options.method === 'POST'; // 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(); if (use_post) { body_data = 'data=' + data['data']; delete data['data']; } 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(options.method, url, true); var headers = this.get_config('xhr_headers'); if (use_post) { headers['Content-Type'] = 'application/x-www-form-urlencoded'; } _.each(headers, function(headerValue, headerName) { req.setRequestHeader(headerName, headerValue); }); // send the mp_optout cookie // withCredentials cannot be modified until after calling .open on Android and Mobile Safari req.withCredentials = true; req.onreadystatechange = function () { if (req.readyState === 4) { // XMLHttpRequest.DONE == 4, except in safari 4 if (req.status === 200) { if (callback) { 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(body_data); } 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 (_.isArray(fn_name)) { tracking_calls.push(item); // chained call e.g. mixpanel.get_group().set() } else 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) { if (_.isArray(item[0])) { // chained call var caller = context; _.each(item, function(call) { caller = caller[call[0]].apply(caller, call.slice(1)); }); } else { 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}); * * @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. * @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. * @param {Function} [callback] If provided, the callback function will be called after tracking the event. */ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, properties, callback) { if (typeof(callback) !== 'function') { callback = function() {}; } if (_.isUndefined(event_name)) { console$1.error('No event name provided to mixpanel.track'); return; } if (this._event_is_disabled(event_name)) { callback(0); return; } // set defaults properties = properties || {}; properties['token'] = this.get_config('token'); // note: extend writes to the first object, so lets make sure we // don't write to the persistence properties object and info // properties object by passing in a new object // update properties with pageview info and super-properties properties = _.extend( {}, _.info.properties(), this['persistence'].properties(), properties ); var property_blacklist = this.get_config('property_blacklist'); if (_.isArray(property_blacklist)) { _.each(property_blacklist, function(blacklisted_prop) { delete properties[blacklisted_prop]; }); } else { console$1.error('Invalid value for property_blacklist config: ' + property_blacklist); } var data = { 'event': event_name, 'properties': properties }; var truncated_data = _.truncate(data, 255); var json_data = _.JSONEncode(truncated_data); var encoded_data = _.base64Encode(json_data); console$1.log('MIXPANEL REQUEST:'); console$1.log(truncated_data); this._send_request( this.get_config('api_host') + '/track/', { 'data': encoded_data }, this._prepare_callback(callback, truncated_data) ); return truncated_data; }); /** * Track a page view event, which is currently ignored by the server. * This function is called by default on page load unless the * track_pageview configuration variable is false. * * @param {String} [page] The url of the page to record. If you don't include this, it defaults to the current url. * @api private */ MixpanelLib.prototype.track_pageview = function(page) { if (_.isUndefined(page)) { page = document$1.location.href; } this.track('mp_page_view', _.info.pageviewInfo(page)); }; /** * 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( new_distinct_id, _set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_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 var previous_distinct_id = this.get_distinct_id(); this.register({'$user_id': new_distinct_id}); if (!this.get_property('$device_id')) { // The persisted distinct id might not actually be a device id at all // it might be a distinct id of the user from before var device_id = previous_distinct_id; this.register_once({ '$had_persisted_distinct_id': true, '$device_id': device_id }, ''); } // 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 (new_distinct_id !== previous_distinct_id && new_distinct_id !== this.get_property(ALIAS_ID_KEY)) { this.unregister(ALIAS_ID_KEY); this.register({'distinct_id': new_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, _remove_callback); // send an $identify event any time the distinct_id is changing - logic on the server // will determine whether or not to do anything with it. if (new_distinct_id !== previous_distinct_id) { this.track('$identify', { 'distinct_id': new_distinct_id, '$anon_distinct_id': previous_distinct_id }); } }; /** * 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; var uuid = _.UUID(); this.register_once({ 'distinct_id': uuid, '$device_id': uuid }, ''); }; /** * Returns the current distinct id of the user. This is either the id automatically * generated by the library or the id that has been passed by a call to identify(). * * ### Notes: * * get_distinct_id() can only be called after the Mixpanel library has finished loading. * init() has a loaded function available to handle this automatically. For example: * * // set distinct_id after the mixpanel library has loaded * mixpanel.init('YOUR PROJECT TOKEN', { * loaded: function(mixpanel) { * distinct_id = mixpanel.get_distinct_id(); * } * }); */ MixpanelLib.prototype.get_distinct_id = function() { return this.get_property('distinct_id'); }; /** * Create an alias, which Mixpanel will use to link two distinct_ids going forward (not retroactively). * Multiple aliases can map to the same original ID, but not vice-versa. Aliases can also be chained - the * following is a valid scenario: * * mixpanel.alias('new_id', 'existing_id'); * ... * mixpanel.alias('newer_id', 'new_id'); * * If the original ID is not passed in, we will use the current distinct_id - probably the auto-generated GUID. * * ### Notes: * * The best practice is to call alias() when a unique ID is first created for a user * (e.g., when a user first registers for an account and provides an email address). * alias() should never be called more than once for a given user, except to * chain a newer ID to a previously new ID, as described above. * * @param {String} alias A unique identifier that you want to use for this user in the future. * @param {String} [original] The current identifier being used for this user. */ MixpanelLib.prototype.alias = function(alias, original) { // If the $people_distinct_id key exists in persistence, there has been a previous // mixpanel.people.identify() call made for this user. It is VERY BAD to make an alias with // this ID, as it will duplicate users. if (alias === this.get_property(PEOPLE_DISTINCT_ID_KEY)) { console$1.critical('Attempting to create alias for existing People user - aborting.'); return -2; } var _this = this; if (_.isUndefined(original)) { original = this.get_distinct_id(); } if (alias !== original) { this._register_single(ALIAS_ID_KEY, alias); return this.track('$create_alias', { 'alias': alias, 'distinct_id': original }, function() { // Flush the people queue _this.identify(alias); }); } else { console$1.error('alias matches current distinct_id - skipping api call.'); this.identify(alias); return -1; } }; /** * Provide a string to recognize the user by. The string passed to * this method will appear in the Mixpanel Streams product rather * than an automatically generated name. Name tags do not have to * be unique. * * This value will only be included in Streams data. * * @param {String} name_tag A human readable name for the user * @api private */ MixpanelLib.prototype.name_tag = function(name_tag) { this._register_single('mp_name_tag', name_tag); }; /** * Update the configuration of a mixpanel library instance. * * The default config is: * * { * // super properties cookie expiration (in days) * cookie_expiration: 365 * * // super properties span subdomains * cross_subdomain_cookie: true * * // debug mode * debug: false * * // if this is true, the mixpanel cookie or localStorage entry * // will be deleted, and no user persistence will take place * disable_persistence: false * * // if this is true, Mixpanel will automatically determine * // City, Region and Country data using the IP address of * //the client * ip: true * * // opt users out of tracking by this Mixpanel instance by default * opt_out_tracking_by_default: false * * // opt users out of browser data storage by this Mixpanel instance by default * opt_out_persistence_by_default: false * * // persistence mechanism used by opt-in/opt-out methods - cookie * // or localStorage - falls back to cookie if localStorage is unavailable * opt_out_tracking_persistence_type: 'localStorage' * * // customize the name of cookie/localStorage set by opt-in/opt-out methods * opt_out_tracking_cookie_prefix: null * * // type of persistent store for super properties (cookie/ * // localStorage) if set to 'localStorage', any existing * // mixpanel cookie value with the same persistence_name * // will be transferred to localStorage and deleted * persistence: 'cookie' * * // name for super properties persistent store * persistence_name: '' * * // names of properties/superproperties which should never * // be sent with track() calls * property_blacklist: [] * * // if this is true, mixpanel cookies will be marked as * // secure, meaning they will only be transmitted over https * secure_cookie: false * * // should we track a page view on page load * track_pageview: true * * // extra HTTP request headers to set for each API request, in * // the format {'Header-Name': value} * xhr_headers: {} * } * * * @param {Object} config A dictionary of new configuration values to update */ MixpanelLib.prototype.set_config = function(config) { if (_.isObject(config)) { _.extend(this['config'], config); if (!this.get_config('persistence_name')) { this['config']['persistence_name'] = this['config']['cookie_name']; } if (!this.get_config('disable_persistence')) { this['config']['disable_persistence'] = this['config']['disable_cookie']; } if (this['persistence']) { this['persistence'].update_config(this['config']); } Config.DEBUG = Config.DEBUG || this.get_config('debug'); } }; /** * returns the current config object for the library. */ MixpanelLib.prototype.get_config = function(prop_name) { return this['config'][prop_name]; }; /** * Returns the value of the super property named property_name. If no such * property is set, get_property() will return the undefined value. * * ### Notes: * * get_property() can only be called after the Mixpanel library has finished loading. * init() has a loaded function available to handle this automatically. For example: * * // grab value for 'user_id' after the mixpanel library has loaded * mixpanel.init('YOUR PROJECT TOKEN', { * loaded: function(mixpanel) { * user_id = mixpanel.get_property('user_id'); * } * }); * * @param {String} property_name The name of the super property you want to retrieve */ MixpanelLib.prototype.get_property = function(property_name) { return this['persistence']['props'][property_name]; }; MixpanelLib.prototype.toString = function() { var name = this.get_config('name'); if (name !== PRIMARY_INSTANCE_NAME) { name = PRIMARY_INSTANCE_NAME + '.' + name; } return name; }; MixpanelLib.prototype._event_is_disabled = function(event_name) { return _.isBlockedUA(userAgent) || this._flags.disable_all_events || _.include(this.__disabled_events, event_name); }; // perform some housekeeping around GDPR opt-in/out state MixpanelLib.prototype._gdpr_init = function() { var is_localStorage_requested = this.get_config('opt_out_tracking_persistence_type') === 'localStorage'; // try to convert opt-in/out cookies to localStorage if possible if (is_localStorage_requested && _.localStorage.is_supported()) { if (!this.has_opted_in_tracking() && this.has_opted_in_tracking({'persistence_type': 'cookie'})) { this.opt_in_tracking({'enable_persistence': false}); } if (!this.has_opted_out_tracking() && this.has_opted_out_tracking({'persistence_type': 'cookie'})) { this.opt_out_tracking({'clear_persistence': false}); } this.clear_opt_in_out_tracking({ 'persistence_type': 'cookie', 'enable_persistence': false }); } // check whether the user has already opted out - if so, clear & disable persistence if (this.has_opted_out_tracking()) { this._gdpr_update_persistence({'clear_persistence': true}); // check whether we should opt out by default // note: we don't clear persistence here by default since opt-out default state is often // used as an initial state while GDPR information is being collected } else if (!this.has_opted_in_tracking() && ( this.get_config('opt_out_tracking_by_default') || _.cookie.get('mp_optout') )) { _.cookie.remove('mp_optout'); this.opt_out_tracking({ 'clear_persistence': this.get_config('opt_out_persistence_by_default') }); } }; /** * Enable or disable persistence based on options * only enable/disable if persistence is not already in this state * @param {boolean} [options.clear_persistence] If true, will delete all data stored by the sdk in persistence and disable it * @param {boolean} [options.enable_persistence] If true, will re-enable sdk persistence */ MixpanelLib.prototype._gdpr_update_persistence = function(options) { var disabled; if (options && options['clear_persistence']) { disabled = true; } else if (options && options['enable_persistence']) { disabled = false; } else { return; } if (!this.get_config('disable_persistence') && this['persistence'].disabled !== disabled) { this['persistence'].set_disabled(disabled); } }; // call a base gdpr function after constructing the appropriate token and options args MixpanelLib.prototype._gdpr_call_func = function(func, options) { options = _.extend({ 'track': this.track.bind(this), 'persistence_type': this.get_config('opt_out_tracking_persistence_type'), 'cookie_prefix': this.get_config('opt_out_tracking_cookie_prefix'), 'cookie_expiration': this.get_config('cookie_expiration'), 'cross_subdomain_cookie': this.get_config('cross_subdomain_cookie'), 'secure_cookie': this.get_config('secure_cookie') }, options); // check if localStorage can be used for recording opt out status, fall back to cookie if not if (!_.localStorage.is_supported()) { options['persistence_type'] = 'cookie'; } return func(this.get_config('token'), { track: options['track'], trackEventName: options['track_event_name'], trackProperties: options['track_properties'], persistenceType: options['persistence_type'], persistencePrefix: options['cookie_prefix'], cookieExpiration: options['cookie_expiration'], crossSubdomainCookie: options['cross_subdomain_cookie'], secureCookie: options['secure_cookie'] }); }; /** * Opt the user in to data tracking and cookies/localstorage for this Mixpanel instance * * ### Usage * * // opt user in * mixpanel.opt_in_tracking(); * * // opt user in with specific event name, properties, cookie configuration * mixpanel.opt_in_tracking({ * track_event_name: 'User opted in', * track_event_properties: { * 'Email': 'jdoe@example.com' * }, * cookie_expiration: 30, * secure_cookie: true * }); * * @param {Object} [options] A dictionary of config options to override * @param {function} [options.track] Function used for tracking a Mixpanel event to record the opt-in action (default is this Mixpanel instance's track method) * @param {string} [options.track_event_name=$opt_in] Event name to be used for tracking the opt-in action * @param {Object} [options.track_properties] Set of properties to be tracked along with the opt-in action * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config) * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config) * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config) */ MixpanelLib.prototype.opt_in_tracking = function(options) { options = _.extend({ 'enable_persistence': true }, options); this._gdpr_call_func(optIn, options); this._gdpr_update_persistence(options); }; /** * Opt the user out of data tracking and cookies/localstorage for this Mixpanel instance * * ### Usage * * // opt user out * mixpanel.opt_out_tracking(); * * // opt user out with different cookie configuration from Mixpanel instance * mixpanel.opt_out_tracking({ * cookie_expiration: 30, * secure_cookie: true * }); * * @param {Object} [options] A dictionary of config options to override * @param {boolean} [options.delete_user=true] If true, will delete the currently identified user's profile and clear all charges after opting the user out * @param {boolean} [options.clear_persistence=true] If true, will delete all data stored by the sdk in persistence * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config) * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config) * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config) */ MixpanelLib.prototype.opt_out_tracking = function(options) { options = _.extend({ 'clear_persistence': true, 'delete_user': true }, options); // delete use and clear charges since these methods may be disabled by opt-out if (options['delete_user'] && this['people'] && this['people']._identify_called()) { this['people'].delete_user(); this['people'].clear_charges(); } this._gdpr_call_func(optOut, options); this._gdpr_update_persistence(options); }; /** * Check whether the user has opted in to data tracking and cookies/localstorage for this Mixpanel instance * * ### Usage * * var has_opted_in = mixpanel.has_opted_in_tracking(); * // use has_opted_in value * * @param {Object} [options] A dictionary of config options to override * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name * @returns {boolean} current opt-in status */ MixpanelLib.prototype.has_opted_in_tracking = function(options) { return this._gdpr_call_func(hasOptedIn, options); }; /** * Check whether the user has opted out of data tracking and cookies/localstorage for this Mixpanel instance * * ### Usage * * var has_opted_out = mixpanel.has_opted_out_tracking(); * // use has_opted_out value * * @param {Object} [options] A dictionary of config options to override * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name * @returns {boolean} current opt-out status */ MixpanelLib.prototype.has_opted_out_tracking = function(options) { return this._gdpr_call_func(hasOptedOut, options); }; /** * Clear the user's opt in/out status of data tracking and cookies/localstorage for this Mixpanel instance * * ### Usage * * // clear user's opt-in/out status * mixpanel.clear_opt_in_out_tracking(); * * // clear user's opt-in/out status with specific cookie configuration - should match * // configuration used when opt_in_tracking/opt_out_tracking methods were called. * mixpanel.clear_opt_in_out_tracking({ * cookie_expiration: 30, * secure_cookie: true * }); * * @param {Object} [options] A dictionary of config options to override * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable * @param {string} [options.cookie_prefix=__mp_opt_in_out] Custom prefix to be used in the cookie/localstorage name * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this Mixpanel instance's config) * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this Mixpanel instance's config) * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this Mixpanel instance's config) */ MixpanelLib.prototype.clear_opt_in_out_tracking = function(options) { options = _.extend({ 'enable_persistence': true }, options); this._gdpr_call_func(clearOptInOut, options); this._gdpr_update_persistence(options); }; // 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['track'] = MixpanelLib.prototype.track; 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['opt_out_tracking'] = MixpanelLib.prototype.opt_out_tracking; MixpanelLib.prototype['opt_in_tracking'] = MixpanelLib.prototype.opt_in_tracking; MixpanelLib.prototype['has_opted_out_tracking'] = MixpanelLib.prototype.has_opted_out_tracking; MixpanelLib.prototype['has_opted_in_tracking'] = MixpanelLib.prototype.has_opted_in_tracking; MixpanelLib.prototype['clear_opt_in_out_tracking'] = MixpanelLib.prototype.clear_opt_in_out_tracking; // MixpanelPersistence Exports MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties; MixpanelPersistence.prototype['get_cross_subdomain'] = MixpanelPersistence.prototype.get_cross_subdomain; MixpanelPersistence.prototype['clear'] = MixpanelPersistence.prototype.clear; _.safewrap_class(MixpanelLib, ['identify']); var instances = {}; var extend_mp = function() { // add all the sub mixpanel instances _.each(instances, function(instance, name) { if (name !== PRIMARY_INSTANCE_NAME) { mixpanel_master[name] = instance; } }); // add private functions as _ mixpanel_master['_'] = _; }; var override_mp_init_func = function() { // we override the snippets init function to handle the case where a // user initializes the mixpanel library after the script loads & runs mixpanel_master['init'] = function(token, config, name) { if (name) { // initialize a sub library if (!mixpanel_master[name]) { mixpanel_master[name] = instances[name] = create_mplib(token, config, name); mixpanel_master[name]._loaded(); } return mixpanel_master[name]; } else { var instance = mixpanel_master; if (instances[PRIMARY_INSTANCE_NAME]) { // main mixpanel lib already initialized instance = instances[PRIMARY_INSTANCE_NAME]; } else if (token) { // intialize the main mixpanel lib instance = create_mplib(token, config, PRIMARY_INSTANCE_NAME); instance._loaded(); instances[PRIMARY_INSTANCE_NAME] = instance; } mixpanel_master = instance; if (init_type === INIT_SNIPPET) { window$1[PRIMARY_INSTANCE_NAME] = mixpanel_master; } extend_mp(); } }; }; var add_dom_loaded_handler = function() { // Cross browser DOM Loaded support function dom_loaded_handler() { // function flag since we only want to execute this once if (dom_loaded_handler.done) { return; } dom_loaded_handler.done = true; DOM_LOADED = true; ENQUEUE_REQUESTS = false; _.each(instances, function(inst) { inst._dom_loaded(); }); } 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); } } }; function init_as_module() { init_type = INIT_MODULE; mixpanel_master = new MixpanelLib(); override_mp_init_func(); mixpanel_master['init'](); add_dom_loaded_handler(); return mixpanel_master; } var mixpanel = init_as_module(); module.exports = mixpanel;