mixpanel-browser 2.40.1 → 2.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@
3
3
 
4
4
  var Config = {
5
5
  DEBUG: false,
6
- LIB_VERSION: '2.40.1'
6
+ LIB_VERSION: '2.43.0'
7
7
  };
8
8
 
9
9
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
@@ -56,7 +56,7 @@
56
56
  };
57
57
 
58
58
  // Console override
59
- var console$1 = {
59
+ var console = {
60
60
  /** @type {function(...*)} */
61
61
  log: function() {
62
62
  if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
@@ -113,14 +113,14 @@
113
113
  var log_func_with_prefix = function(func, prefix) {
114
114
  return function() {
115
115
  arguments[0] = '[' + prefix + '] ' + arguments[0];
116
- return func.apply(console$1, arguments);
116
+ return func.apply(console, arguments);
117
117
  };
118
118
  };
119
119
  var console_with_prefix = function(prefix) {
120
120
  return {
121
- log: log_func_with_prefix(console$1.log, prefix),
122
- error: log_func_with_prefix(console$1.error, prefix),
123
- critical: log_func_with_prefix(console$1.critical, prefix)
121
+ log: log_func_with_prefix(console.log, prefix),
122
+ error: log_func_with_prefix(console.error, prefix),
123
+ critical: log_func_with_prefix(console.critical, prefix)
124
124
  };
125
125
  };
126
126
 
@@ -282,10 +282,6 @@
282
282
  return results;
283
283
  };
284
284
 
285
- _.identity = function(value) {
286
- return value;
287
- };
288
-
289
285
  _.include = function(obj, target) {
290
286
  var found = false;
291
287
  if (obj === null) {
@@ -386,9 +382,9 @@
386
382
  try {
387
383
  return f.apply(this, arguments);
388
384
  } catch (e) {
389
- console$1.critical('Implementation error. Please turn on debug and contact support@mixpanel.com.');
385
+ console.critical('Implementation error. Please turn on debug and contact support@mixpanel.com.');
390
386
  if (Config.DEBUG){
391
- console$1.critical(e);
387
+ console.critical(e);
392
388
  }
393
389
  }
394
390
  };
@@ -949,10 +945,12 @@
949
945
  // This is to block various web spiders from executing our JS and
950
946
  // sending false tracking data
951
947
  var BLOCKED_UA_STRS = [
948
+ 'ahrefsbot',
952
949
  'baiduspider',
953
950
  'bingbot',
954
951
  'bingpreview',
955
952
  'facebookexternal',
953
+ 'petalbot',
956
954
  'pinterest',
957
955
  'screaming frog',
958
956
  'yahoo! slurp',
@@ -1017,16 +1015,12 @@
1017
1015
  try {
1018
1016
  result = decodeURIComponent(result);
1019
1017
  } catch(err) {
1020
- console$1.error('Skipping decoding for malformed query param: ' + result);
1018
+ console.error('Skipping decoding for malformed query param: ' + result);
1021
1019
  }
1022
1020
  return result.replace(/\+/g, ' ');
1023
1021
  }
1024
1022
  };
1025
1023
 
1026
- _.getHashParam = function(hash, param) {
1027
- var matches = hash.match(new RegExp(param + '=([^&]*)'));
1028
- return matches ? matches[1] : null;
1029
- };
1030
1024
 
1031
1025
  // _.cookie
1032
1026
  // Methods partially borrowed from quirksmode.org/js/cookies.html
@@ -1148,13 +1142,13 @@
1148
1142
  is_supported: function(force_check) {
1149
1143
  var supported = localStorageSupported(null, force_check);
1150
1144
  if (!supported) {
1151
- console$1.error('localStorage unsupported; falling back to cookie store');
1145
+ console.error('localStorage unsupported; falling back to cookie store');
1152
1146
  }
1153
1147
  return supported;
1154
1148
  },
1155
1149
 
1156
1150
  error: function(msg) {
1157
- console$1.error('localStorage error: ' + msg);
1151
+ console.error('localStorage error: ' + msg);
1158
1152
  },
1159
1153
 
1160
1154
  get: function(name) {
@@ -1209,7 +1203,7 @@
1209
1203
  */
1210
1204
  var register_event = function(element, type, handler, oldSchool, useCapture) {
1211
1205
  if (!element) {
1212
- console$1.error('No valid element provided to register_event');
1206
+ console.error('No valid element provided to register_event');
1213
1207
  return;
1214
1208
  }
1215
1209
 
@@ -1693,28 +1687,6 @@
1693
1687
  return maxlen ? guid.substring(0, maxlen) : guid;
1694
1688
  };
1695
1689
 
1696
- /**
1697
- * Check deterministically whether to include or exclude from a feature rollout/test based on the
1698
- * given string and the desired percentage to include.
1699
- * @param {String} str - string to run the check against (for instance a project's token)
1700
- * @param {String} feature - name of feature (for inclusion in hash, to ensure different results
1701
- * for different features)
1702
- * @param {Number} percent_allowed - percentage chance that a given string will be included
1703
- * @returns {Boolean} whether the given string should be included
1704
- */
1705
- var determine_eligibility = _.safewrap(function(str, feature, percent_allowed) {
1706
- str = str + feature;
1707
-
1708
- // Bernstein's hash: http://www.cse.yorku.ca/~oz/hash.html#djb2
1709
- var hash = 5381;
1710
- for (var i = 0; i < str.length; i++) {
1711
- hash = ((hash << 5) + hash) + str.charCodeAt(i);
1712
- hash = hash & hash;
1713
- }
1714
- var dart = (hash >>> 0) % 100;
1715
- return dart < percent_allowed;
1716
- });
1717
-
1718
1690
  // naive way to extract domain name (example.com) from full hostname (my.sub.example.com)
1719
1691
  var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i;
1720
1692
  // this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk
@@ -1765,539 +1737,6 @@
1765
1737
  _['info']['browserVersion'] = _.info.browserVersion;
1766
1738
  _['info']['properties'] = _.info.properties;
1767
1739
 
1768
- /*
1769
- * Get the className of an element, accounting for edge cases where element.className is an object
1770
- * @param {Element} el - element to get the className of
1771
- * @returns {string} the element's class
1772
- */
1773
- function getClassName(el) {
1774
- switch(typeof el.className) {
1775
- case 'string':
1776
- return el.className;
1777
- case 'object': // handle cases where className might be SVGAnimatedString or some other type
1778
- return el.className.baseVal || el.getAttribute('class') || '';
1779
- default: // future proof
1780
- return '';
1781
- }
1782
- }
1783
-
1784
- /*
1785
- * Get the direct text content of an element, protecting against sensitive data collection.
1786
- * Concats textContent of each of the element's text node children; this avoids potential
1787
- * collection of sensitive data that could happen if we used element.textContent and the
1788
- * element had sensitive child elements, since element.textContent includes child content.
1789
- * Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
1790
- * @param {Element} el - element to get the text of
1791
- * @returns {string} the element's direct text content
1792
- */
1793
- function getSafeText(el) {
1794
- var elText = '';
1795
-
1796
- if (shouldTrackElement(el) && el.childNodes && el.childNodes.length) {
1797
- _.each(el.childNodes, function(child) {
1798
- if (isTextNode(child) && child.textContent) {
1799
- elText += _.trim(child.textContent)
1800
- // scrub potentially sensitive values
1801
- .split(/(\s+)/).filter(shouldTrackValue).join('')
1802
- // normalize whitespace
1803
- .replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ')
1804
- // truncate
1805
- .substring(0, 255);
1806
- }
1807
- });
1808
- }
1809
-
1810
- return _.trim(elText);
1811
- }
1812
-
1813
- /*
1814
- * Check whether an element has nodeType Node.ELEMENT_NODE
1815
- * @param {Element} el - element to check
1816
- * @returns {boolean} whether el is of the correct nodeType
1817
- */
1818
- function isElementNode(el) {
1819
- return el && el.nodeType === 1; // Node.ELEMENT_NODE - use integer constant for browser portability
1820
- }
1821
-
1822
- /*
1823
- * Check whether an element is of a given tag type.
1824
- * Due to potential reference discrepancies (such as the webcomponents.js polyfill),
1825
- * we want to match tagNames instead of specific references because something like
1826
- * element === document.body won't always work because element might not be a native
1827
- * element.
1828
- * @param {Element} el - element to check
1829
- * @param {string} tag - tag name (e.g., "div")
1830
- * @returns {boolean} whether el is of the given tag type
1831
- */
1832
- function isTag(el, tag) {
1833
- return el && el.tagName && el.tagName.toLowerCase() === tag.toLowerCase();
1834
- }
1835
-
1836
- /*
1837
- * Check whether an element has nodeType Node.TEXT_NODE
1838
- * @param {Element} el - element to check
1839
- * @returns {boolean} whether el is of the correct nodeType
1840
- */
1841
- function isTextNode(el) {
1842
- return el && el.nodeType === 3; // Node.TEXT_NODE - use integer constant for browser portability
1843
- }
1844
-
1845
- /*
1846
- * Check whether a DOM event should be "tracked" or if it may contain sentitive data
1847
- * using a variety of heuristics.
1848
- * @param {Element} el - element to check
1849
- * @param {Event} event - event to check
1850
- * @returns {boolean} whether the event should be tracked
1851
- */
1852
- function shouldTrackDomEvent(el, event) {
1853
- if (!el || isTag(el, 'html') || !isElementNode(el)) {
1854
- return false;
1855
- }
1856
- var tag = el.tagName.toLowerCase();
1857
- switch (tag) {
1858
- case 'html':
1859
- return false;
1860
- case 'form':
1861
- return event.type === 'submit';
1862
- case 'input':
1863
- if (['button', 'submit'].indexOf(el.getAttribute('type')) === -1) {
1864
- return event.type === 'change';
1865
- } else {
1866
- return event.type === 'click';
1867
- }
1868
- case 'select':
1869
- case 'textarea':
1870
- return event.type === 'change';
1871
- default:
1872
- return event.type === 'click';
1873
- }
1874
- }
1875
-
1876
- /*
1877
- * Check whether a DOM element should be "tracked" or if it may contain sentitive data
1878
- * using a variety of heuristics.
1879
- * @param {Element} el - element to check
1880
- * @returns {boolean} whether the element should be tracked
1881
- */
1882
- function shouldTrackElement(el) {
1883
- for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
1884
- var classes = getClassName(curEl).split(' ');
1885
- if (_.includes(classes, 'mp-sensitive') || _.includes(classes, 'mp-no-track')) {
1886
- return false;
1887
- }
1888
- }
1889
-
1890
- if (_.includes(getClassName(el).split(' '), 'mp-include')) {
1891
- return true;
1892
- }
1893
-
1894
- // don't send data from inputs or similar elements since there will always be
1895
- // a risk of clientside javascript placing sensitive data in attributes
1896
- if (
1897
- isTag(el, 'input') ||
1898
- isTag(el, 'select') ||
1899
- isTag(el, 'textarea') ||
1900
- el.getAttribute('contenteditable') === 'true'
1901
- ) {
1902
- return false;
1903
- }
1904
-
1905
- // don't include hidden or password fields
1906
- var type = el.type || '';
1907
- if (typeof type === 'string') { // it's possible for el.type to be a DOM element if el is a form with a child input[name="type"]
1908
- switch(type.toLowerCase()) {
1909
- case 'hidden':
1910
- return false;
1911
- case 'password':
1912
- return false;
1913
- }
1914
- }
1915
-
1916
- // filter out data from fields that look like sensitive fields
1917
- var name = el.name || el.id || '';
1918
- if (typeof name === 'string') { // it's possible for el.name or el.id to be a DOM element if el is a form with a child input[name="name"]
1919
- var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
1920
- if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
1921
- return false;
1922
- }
1923
- }
1924
-
1925
- return true;
1926
- }
1927
-
1928
- /*
1929
- * Check whether a string value should be "tracked" or if it may contain sentitive data
1930
- * using a variety of heuristics.
1931
- * @param {string} value - string value to check
1932
- * @returns {boolean} whether the element should be tracked
1933
- */
1934
- function shouldTrackValue(value) {
1935
- if (value === null || _.isUndefined(value)) {
1936
- return false;
1937
- }
1938
-
1939
- if (typeof value === 'string') {
1940
- value = _.trim(value);
1941
-
1942
- // check to see if input value looks like a credit card number
1943
- // see: https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s20.html
1944
- var ccRegex = /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/;
1945
- if (ccRegex.test((value || '').replace(/[- ]/g, ''))) {
1946
- return false;
1947
- }
1948
-
1949
- // check to see if input value looks like a social security number
1950
- var ssnRegex = /(^\d{3}-?\d{2}-?\d{4}$)/;
1951
- if (ssnRegex.test(value)) {
1952
- return false;
1953
- }
1954
- }
1955
-
1956
- return true;
1957
- }
1958
-
1959
- var autotrack = {
1960
- _initializedTokens: [],
1961
-
1962
- _previousElementSibling: function(el) {
1963
- if (el.previousElementSibling) {
1964
- return el.previousElementSibling;
1965
- } else {
1966
- do {
1967
- el = el.previousSibling;
1968
- } while (el && !isElementNode(el));
1969
- return el;
1970
- }
1971
- },
1972
-
1973
- _loadScript: function(scriptUrlToLoad, callback) {
1974
- var scriptTag = document.createElement('script');
1975
- scriptTag.type = 'text/javascript';
1976
- scriptTag.src = scriptUrlToLoad;
1977
- scriptTag.onload = callback;
1978
-
1979
- var scripts = document.getElementsByTagName('script');
1980
- if (scripts.length > 0) {
1981
- scripts[0].parentNode.insertBefore(scriptTag, scripts[0]);
1982
- } else {
1983
- document.body.appendChild(scriptTag);
1984
- }
1985
- },
1986
-
1987
- _getPropertiesFromElement: function(elem) {
1988
- var props = {
1989
- 'classes': getClassName(elem).split(' '),
1990
- 'tag_name': elem.tagName.toLowerCase()
1991
- };
1992
-
1993
- if (shouldTrackElement(elem)) {
1994
- _.each(elem.attributes, function(attr) {
1995
- if (shouldTrackValue(attr.value)) {
1996
- props['attr__' + attr.name] = attr.value;
1997
- }
1998
- });
1999
- }
2000
-
2001
- var nthChild = 1;
2002
- var nthOfType = 1;
2003
- var currentElem = elem;
2004
- while (currentElem = this._previousElementSibling(currentElem)) { // eslint-disable-line no-cond-assign
2005
- nthChild++;
2006
- if (currentElem.tagName === elem.tagName) {
2007
- nthOfType++;
2008
- }
2009
- }
2010
- props['nth_child'] = nthChild;
2011
- props['nth_of_type'] = nthOfType;
2012
-
2013
- return props;
2014
- },
2015
-
2016
- _getDefaultProperties: function(eventType) {
2017
- return {
2018
- '$event_type': eventType,
2019
- '$ce_version': 1,
2020
- '$host': window.location.host,
2021
- '$pathname': window.location.pathname
2022
- };
2023
- },
2024
-
2025
- _extractCustomPropertyValue: function(customProperty) {
2026
- var propValues = [];
2027
- _.each(document.querySelectorAll(customProperty['css_selector']), function(matchedElem) {
2028
- var value;
2029
-
2030
- if (['input', 'select'].indexOf(matchedElem.tagName.toLowerCase()) > -1) {
2031
- value = matchedElem['value'];
2032
- } else if (matchedElem['textContent']) {
2033
- value = matchedElem['textContent'];
2034
- }
2035
-
2036
- if (shouldTrackValue(value)) {
2037
- propValues.push(value);
2038
- }
2039
- });
2040
- return propValues.join(', ');
2041
- },
2042
-
2043
- _getCustomProperties: function(targetElementList) {
2044
- var props = {};
2045
- _.each(this._customProperties, function(customProperty) {
2046
- _.each(customProperty['event_selectors'], function(eventSelector) {
2047
- var eventElements = document.querySelectorAll(eventSelector);
2048
- _.each(eventElements, function(eventElement) {
2049
- if (_.includes(targetElementList, eventElement) && shouldTrackElement(eventElement)) {
2050
- props[customProperty['name']] = this._extractCustomPropertyValue(customProperty);
2051
- }
2052
- }, this);
2053
- }, this);
2054
- }, this);
2055
- return props;
2056
- },
2057
-
2058
- _getEventTarget: function(e) {
2059
- // https://developer.mozilla.org/en-US/docs/Web/API/Event/target#Compatibility_notes
2060
- if (typeof e.target === 'undefined') {
2061
- return e.srcElement;
2062
- } else {
2063
- return e.target;
2064
- }
2065
- },
2066
-
2067
- _trackEvent: function(e, instance) {
2068
- /*** Don't mess with this code without running IE8 tests on it ***/
2069
- var target = this._getEventTarget(e);
2070
- if (isTextNode(target)) { // defeat Safari bug (see: http://www.quirksmode.org/js/events_properties.html)
2071
- target = target.parentNode;
2072
- }
2073
-
2074
- if (shouldTrackDomEvent(target, e)) {
2075
- var targetElementList = [target];
2076
- var curEl = target;
2077
- while (curEl.parentNode && !isTag(curEl, 'body')) {
2078
- targetElementList.push(curEl.parentNode);
2079
- curEl = curEl.parentNode;
2080
- }
2081
-
2082
- var elementsJson = [];
2083
- var href, explicitNoTrack = false;
2084
- _.each(targetElementList, function(el) {
2085
- var shouldTrackEl = shouldTrackElement(el);
2086
-
2087
- // if the element or a parent element is an anchor tag
2088
- // include the href as a property
2089
- if (el.tagName.toLowerCase() === 'a') {
2090
- href = el.getAttribute('href');
2091
- href = shouldTrackEl && shouldTrackValue(href) && href;
2092
- }
2093
-
2094
- // allow users to programatically prevent tracking of elements by adding class 'mp-no-track'
2095
- var classes = getClassName(el).split(' ');
2096
- if (_.includes(classes, 'mp-no-track')) {
2097
- explicitNoTrack = true;
2098
- }
2099
-
2100
- elementsJson.push(this._getPropertiesFromElement(el));
2101
- }, this);
2102
-
2103
- if (explicitNoTrack) {
2104
- return false;
2105
- }
2106
-
2107
- // only populate text content from target element (not parents)
2108
- // to prevent text within a sensitive element from being collected
2109
- // as part of a parent's el.textContent
2110
- var elementText;
2111
- var safeElementText = getSafeText(target);
2112
- if (safeElementText && safeElementText.length) {
2113
- elementText = safeElementText;
2114
- }
2115
-
2116
- var props = _.extend(
2117
- this._getDefaultProperties(e.type),
2118
- {
2119
- '$elements': elementsJson,
2120
- '$el_attr__href': href,
2121
- '$el_text': elementText
2122
- },
2123
- this._getCustomProperties(targetElementList)
2124
- );
2125
-
2126
- instance.track('$web_event', props);
2127
- return true;
2128
- }
2129
- },
2130
-
2131
- // only reason is to stub for unit tests
2132
- // since you can't override window.location props
2133
- _navigate: function(href) {
2134
- window.location.href = href;
2135
- },
2136
-
2137
- _addDomEventHandlers: function(instance) {
2138
- var handler = _.bind(function(e) {
2139
- e = e || window.event;
2140
- this._trackEvent(e, instance);
2141
- }, this);
2142
- _.register_event(document, 'submit', handler, false, true);
2143
- _.register_event(document, 'change', handler, false, true);
2144
- _.register_event(document, 'click', handler, false, true);
2145
- },
2146
-
2147
- _customProperties: {},
2148
- init: function(instance) {
2149
- if (!(document && document.body)) {
2150
- console.log('document not ready yet, trying again in 500 milliseconds...');
2151
- var that = this;
2152
- setTimeout(function() { that.init(instance); }, 500);
2153
- return;
2154
- }
2155
-
2156
- var token = instance.get_config('token');
2157
- if (this._initializedTokens.indexOf(token) > -1) {
2158
- console.log('autotrack already initialized for token "' + token + '"');
2159
- return;
2160
- }
2161
- this._initializedTokens.push(token);
2162
-
2163
- if (!this._maybeLoadEditor(instance)) { // don't autotrack actions when the editor is enabled
2164
- var parseDecideResponse = _.bind(function(response) {
2165
- if (response && response['config'] && response['config']['enable_collect_everything'] === true) {
2166
-
2167
- if (response['custom_properties']) {
2168
- this._customProperties = response['custom_properties'];
2169
- }
2170
-
2171
- instance.track('$web_event', _.extend({
2172
- '$title': document.title
2173
- }, this._getDefaultProperties('pageview')));
2174
-
2175
- this._addDomEventHandlers(instance);
2176
-
2177
- } else {
2178
- instance['__autotrack_enabled'] = false;
2179
- }
2180
- }, this);
2181
-
2182
- instance._send_request(
2183
- instance.get_config('api_host') + '/decide/', {
2184
- 'verbose': true,
2185
- 'version': '1',
2186
- 'lib': 'web',
2187
- 'token': token
2188
- },
2189
- {method: 'GET', transport: 'XHR'},
2190
- instance._prepare_callback(parseDecideResponse)
2191
- );
2192
- }
2193
- },
2194
-
2195
- _editorParamsFromHash: function(instance, hash) {
2196
- var editorParams;
2197
- try {
2198
- var state = _.getHashParam(hash, 'state');
2199
- state = JSON.parse(decodeURIComponent(state));
2200
- var expiresInSeconds = _.getHashParam(hash, 'expires_in');
2201
- editorParams = {
2202
- 'accessToken': _.getHashParam(hash, 'access_token'),
2203
- 'accessTokenExpiresAt': (new Date()).getTime() + (Number(expiresInSeconds) * 1000),
2204
- 'bookmarkletMode': !!state['bookmarkletMode'],
2205
- 'projectId': state['projectId'],
2206
- 'projectOwnerId': state['projectOwnerId'],
2207
- 'projectToken': state['token'],
2208
- 'readOnly': state['readOnly'],
2209
- 'userFlags': state['userFlags'],
2210
- 'userId': state['userId']
2211
- };
2212
- window.sessionStorage.setItem('editorParams', JSON.stringify(editorParams));
2213
-
2214
- if (state['desiredHash']) {
2215
- window.location.hash = state['desiredHash'];
2216
- } else if (window.history) {
2217
- history.replaceState('', document.title, window.location.pathname + window.location.search); // completely remove hash
2218
- } else {
2219
- window.location.hash = ''; // clear hash (but leaves # unfortunately)
2220
- }
2221
- } catch (e) {
2222
- console.error('Unable to parse data from hash', e);
2223
- }
2224
- return editorParams;
2225
- },
2226
-
2227
- /**
2228
- * To load the visual editor, we need an access token and other state. That state comes from one of three places:
2229
- * 1. In the URL hash params if the customer is using an old snippet
2230
- * 2. From session storage under the key `_mpcehash` if the snippet already parsed the hash
2231
- * 3. From session storage under the key `editorParams` if the editor was initialized on a previous page
2232
- */
2233
- _maybeLoadEditor: function(instance) {
2234
- try {
2235
- var parseFromUrl = false;
2236
- if (_.getHashParam(window.location.hash, 'state')) {
2237
- var state = _.getHashParam(window.location.hash, 'state');
2238
- state = JSON.parse(decodeURIComponent(state));
2239
- parseFromUrl = state['action'] === 'mpeditor';
2240
- }
2241
- var parseFromStorage = !!window.sessionStorage.getItem('_mpcehash');
2242
- var editorParams;
2243
-
2244
- if (parseFromUrl) { // happens if they are initializing the editor using an old snippet
2245
- editorParams = this._editorParamsFromHash(instance, window.location.hash);
2246
- } else if (parseFromStorage) { // happens if they are initialized the editor and using the new snippet
2247
- editorParams = this._editorParamsFromHash(instance, window.sessionStorage.getItem('_mpcehash'));
2248
- window.sessionStorage.removeItem('_mpcehash');
2249
- } else { // get credentials from sessionStorage from a previous initialzation
2250
- editorParams = JSON.parse(window.sessionStorage.getItem('editorParams') || '{}');
2251
- }
2252
-
2253
- if (editorParams['projectToken'] && instance.get_config('token') === editorParams['projectToken']) {
2254
- this._loadEditor(instance, editorParams);
2255
- return true;
2256
- } else {
2257
- return false;
2258
- }
2259
- } catch (e) {
2260
- return false;
2261
- }
2262
- },
2263
-
2264
- _loadEditor: function(instance, editorParams) {
2265
- if (!window['_mpEditorLoaded']) { // only load the codeless event editor once, even if there are multiple instances of MixpanelLib
2266
- window['_mpEditorLoaded'] = true;
2267
- var editorUrl = instance.get_config('app_host')
2268
- + '/js-bundle/reports/collect-everything/editor.js?_ts='
2269
- + (new Date()).getTime();
2270
- this._loadScript(editorUrl, function() {
2271
- window['mp_load_editor'](editorParams);
2272
- });
2273
- return true;
2274
- }
2275
- return false;
2276
- },
2277
-
2278
- // this is a mechanism to ramp up CE with no server-side interaction.
2279
- // when CE is active, every page load results in a decide request. we
2280
- // need to gently ramp this up so we don't overload decide. this decides
2281
- // deterministically if CE is enabled for this project by modding the char
2282
- // value of the project token.
2283
- enabledForProject: function(token, numBuckets, numEnabledBuckets) {
2284
- numBuckets = !_.isUndefined(numBuckets) ? numBuckets : 10;
2285
- numEnabledBuckets = !_.isUndefined(numEnabledBuckets) ? numEnabledBuckets : 10;
2286
- var charCodeSum = 0;
2287
- for (var i = 0; i < token.length; i++) {
2288
- charCodeSum += token.charCodeAt(i);
2289
- }
2290
- return (charCodeSum % numBuckets) < numEnabledBuckets;
2291
- },
2292
-
2293
- isBrowserSupported: function() {
2294
- return _.isFunction(document.querySelectorAll);
2295
- }
2296
- };
2297
-
2298
- _.bind_instance_methods(autotrack);
2299
- _.safewrap_instance_methods(autotrack);
2300
-
2301
1740
  /**
2302
1741
  * DomTracker Object
2303
1742
  * @constructor
@@ -2326,7 +1765,7 @@
2326
1765
  var elements = _.dom_query(query);
2327
1766
 
2328
1767
  if (elements.length === 0) {
2329
- console$1.error('The DOM query (' + query + ') returned 0 elements');
1768
+ console.error('The DOM query (' + query + ') returned 0 elements');
2330
1769
  return;
2331
1770
  }
2332
1771
 
@@ -2986,9 +2425,9 @@
2986
2425
  } else if (
2987
2426
  _.isObject(res) &&
2988
2427
  res.xhr_req &&
2989
- (res.xhr_req['status'] >= 500 || res.xhr_req['status'] <= 0)
2428
+ (res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout')
2990
2429
  ) {
2991
- // network or API error, retry
2430
+ // network or API error, or 429 Too Many Requests, retry
2992
2431
  var retryMS = this.flushInterval * 2;
2993
2432
  var headers = res.xhr_req['responseHeaders'];
2994
2433
  if (headers) {
@@ -3118,12 +2557,12 @@
3118
2557
  */
3119
2558
  function hasOptedOut(token, options) {
3120
2559
  if (_hasDoNotTrackFlagOn(options)) {
3121
- console$1.warn('This browser has "Do Not Track" enabled. This will prevent the Mixpanel SDK from sending any data. To ignore the "Do Not Track" browser setting, initialize the Mixpanel instance with the config "ignore_dnt: true"');
2560
+ console.warn('This browser has "Do Not Track" enabled. This will prevent the Mixpanel SDK from sending any data. To ignore the "Do Not Track" browser setting, initialize the Mixpanel instance with the config "ignore_dnt: true"');
3122
2561
  return true;
3123
2562
  }
3124
2563
  var optedOut = _getStorageValue(token, options) === '0';
3125
2564
  if (optedOut) {
3126
- console$1.warn('You are opted out of Mixpanel tracking. This will prevent the Mixpanel SDK from sending any data.');
2565
+ console.warn('You are opted out of Mixpanel tracking. This will prevent the Mixpanel SDK from sending any data.');
3127
2566
  }
3128
2567
  return optedOut;
3129
2568
  }
@@ -3267,7 +2706,7 @@
3267
2706
  */
3268
2707
  function _optInOut(optValue, token, options) {
3269
2708
  if (!_.isString(token) || !token.length) {
3270
- console$1.error('gdpr.' + (optValue ? 'optIn' : 'optOut') + ' called with an invalid token');
2709
+ console.error('gdpr.' + (optValue ? 'optIn' : 'optOut') + ' called with an invalid token');
3271
2710
  return;
3272
2711
  }
3273
2712
 
@@ -3318,7 +2757,7 @@
3318
2757
  });
3319
2758
  }
3320
2759
  } catch(err) {
3321
- console$1.error('Unexpected error when checking tracking opt-out status: ' + err);
2760
+ console.error('Unexpected error when checking tracking opt-out status: ' + err);
3322
2761
  }
3323
2762
 
3324
2763
  if (!optedOut) {
@@ -3558,9 +2997,13 @@
3558
2997
  * Permanently delete a group.
3559
2998
  *
3560
2999
  * ### Usage:
3000
+ *
3561
3001
  * mixpanel.get_group('company', 'mixpanel').delete();
3002
+ *
3003
+ * @param {Function} [callback] If provided, the callback will be called after the tracking event
3562
3004
  */
3563
3005
  MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) {
3006
+ // bracket notation above prevents a minification error related to reserved words
3564
3007
  var data = this.delete_action();
3565
3008
  return this._send_request(data, callback);
3566
3009
  });
@@ -3660,7 +3103,7 @@
3660
3103
 
3661
3104
  var storage_type = config['persistence'];
3662
3105
  if (storage_type !== 'cookie' && storage_type !== 'localStorage') {
3663
- console$1.critical('Unknown persistence type ' + storage_type + '; falling back to cookie');
3106
+ console.critical('Unknown persistence type ' + storage_type + '; falling back to cookie');
3664
3107
  storage_type = config['persistence'] = 'cookie';
3665
3108
  }
3666
3109
 
@@ -4018,8 +3461,8 @@
4018
3461
  this._pop_from_people_queue(UNSET_ACTION, q_data);
4019
3462
  }
4020
3463
 
4021
- console$1.log('MIXPANEL PEOPLE REQUEST (QUEUED, PENDING IDENTIFY):');
4022
- console$1.log(data);
3464
+ console.log('MIXPANEL PEOPLE REQUEST (QUEUED, PENDING IDENTIFY):');
3465
+ console.log(data);
4023
3466
 
4024
3467
  this.save();
4025
3468
  };
@@ -4062,7 +3505,7 @@
4062
3505
  } else if (queue === UNION_ACTION) {
4063
3506
  return UNION_QUEUE_KEY;
4064
3507
  } else {
4065
- console$1.error('Invalid queue:', queue);
3508
+ console.error('Invalid queue:', queue);
4066
3509
  }
4067
3510
  };
4068
3511
 
@@ -6030,7 +5473,7 @@
6030
5473
  _.each(prop, function(v, k) {
6031
5474
  if (!this._is_reserved_property(k)) {
6032
5475
  if (isNaN(parseFloat(v))) {
6033
- console$1.error('Invalid increment value passed to mixpanel.people.increment - must be a number');
5476
+ console.error('Invalid increment value passed to mixpanel.people.increment - must be a number');
6034
5477
  return;
6035
5478
  } else {
6036
5479
  $add[k] = v;
@@ -6154,7 +5597,7 @@
6154
5597
  if (!_.isNumber(amount)) {
6155
5598
  amount = parseFloat(amount);
6156
5599
  if (isNaN(amount)) {
6157
- console$1.error('Invalid value passed to mixpanel.people.track_charge - must be a number');
5600
+ console.error('Invalid value passed to mixpanel.people.track_charge - must be a number');
6158
5601
  return;
6159
5602
  }
6160
5603
  }
@@ -6190,7 +5633,7 @@
6190
5633
  */
6191
5634
  MixpanelPeople.prototype.delete_user = function() {
6192
5635
  if (!this._identify_called()) {
6193
- console$1.error('mixpanel.people.delete_user() requires you to call identify() first');
5636
+ console.error('mixpanel.people.delete_user() requires you to call identify() first');
6194
5637
  return;
6195
5638
  }
6196
5639
  var data = {'$delete': this._mixpanel.get_distinct_id()};
@@ -6264,7 +5707,7 @@
6264
5707
  } else if (UNION_ACTION in data) {
6265
5708
  this._mixpanel['persistence']._add_to_people_queue(UNION_ACTION, data);
6266
5709
  } else {
6267
- console$1.error('Invalid call to _enqueue():', data);
5710
+ console.error('Invalid call to _enqueue():', data);
6268
5711
  }
6269
5712
  };
6270
5713
 
@@ -6401,6 +5844,8 @@
6401
5844
  var NOOP_FUNC = function() {};
6402
5845
 
6403
5846
  /** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
5847
+ /** @const */ var PAYLOAD_TYPE_BASE64 = 'base64';
5848
+ /** @const */ var PAYLOAD_TYPE_JSON = 'json';
6404
5849
 
6405
5850
 
6406
5851
  /*
@@ -6431,8 +5876,8 @@
6431
5876
  'api_host': 'https://api-js.mixpanel.com',
6432
5877
  'api_method': 'POST',
6433
5878
  'api_transport': 'XHR',
5879
+ 'api_payload_format': PAYLOAD_TYPE_BASE64,
6434
5880
  'app_host': 'https://mixpanel.com',
6435
- 'autotrack': true,
6436
5881
  'cdn': 'https://cdn.mxpnl.com',
6437
5882
  'cross_site_cookie': false,
6438
5883
  'cross_subdomain_cookie': true,
@@ -6463,7 +5908,7 @@
6463
5908
  'inapp_protocol': '//',
6464
5909
  'inapp_link_new_window': false,
6465
5910
  'ignore_dnt': false,
6466
- 'batch_requests': false, // for now
5911
+ 'batch_requests': true,
6467
5912
  'batch_size': 50,
6468
5913
  'batch_flush_interval_ms': 5000,
6469
5914
  'batch_request_timeout_ms': 90000,
@@ -6496,7 +5941,7 @@
6496
5941
  instance = target;
6497
5942
  } else {
6498
5943
  if (target && !_.isArray(target)) {
6499
- console$1.error('You have already initialized ' + name);
5944
+ console.error('You have already initialized ' + name);
6500
5945
  return;
6501
5946
  }
6502
5947
  instance = new MixpanelLib();
@@ -6515,21 +5960,6 @@
6515
5960
  // global debug to be true
6516
5961
  Config.DEBUG = Config.DEBUG || instance.get_config('debug');
6517
5962
 
6518
- instance['__autotrack_enabled'] = instance.get_config('autotrack');
6519
- if (instance.get_config('autotrack')) {
6520
- var num_buckets = 100;
6521
- var num_enabled_buckets = 100;
6522
- if (!autotrack.enabledForProject(instance.get_config('token'), num_buckets, num_enabled_buckets)) {
6523
- instance['__autotrack_enabled'] = false;
6524
- console$1.log('Not in active bucket: disabling Automatic Event Collection.');
6525
- } else if (!autotrack.isBrowserSupported()) {
6526
- instance['__autotrack_enabled'] = false;
6527
- console$1.log('Disabling Automatic Event Collection because this browser is not supported');
6528
- } else {
6529
- autotrack.init(instance);
6530
- }
6531
- }
6532
-
6533
5963
  // if target is not defined, we called init after the lib already
6534
5964
  // loaded, so there won't be an array of things to execute
6535
5965
  if (!_.isUndefined(target) && _.isArray(target)) {
@@ -6542,12 +5972,6 @@
6542
5972
  return instance;
6543
5973
  };
6544
5974
 
6545
- var encode_data_for_request = function(data) {
6546
- var json_data = _.JSONEncode(data);
6547
- var encoded_data = _.base64Encode(json_data);
6548
- return {'data': encoded_data};
6549
- };
6550
-
6551
5975
  // Initialization methods
6552
5976
 
6553
5977
  /**
@@ -6568,11 +5992,11 @@
6568
5992
  */
6569
5993
  MixpanelLib.prototype.init = function (token, config, name) {
6570
5994
  if (_.isUndefined(name)) {
6571
- console$1.error('You must name your new library: init(token, config, name)');
5995
+ console.error('You must name your new library: init(token, config, name)');
6572
5996
  return;
6573
5997
  }
6574
5998
  if (name === PRIMARY_INSTANCE_NAME) {
6575
- console$1.error('You must initialize the main mixpanel object right after you include the Mixpanel js snippet');
5999
+ console.error('You must initialize the main mixpanel object right after you include the Mixpanel js snippet');
6576
6000
  return;
6577
6001
  }
6578
6002
 
@@ -6597,14 +6021,14 @@
6597
6021
  this['config'] = {};
6598
6022
  this['_triggered_notifs'] = [];
6599
6023
 
6600
- // rollout: enable batch_requests by default for 60% of projects
6601
- // (only if they have not specified a value in their init config
6602
- // and they aren't using a custom API host)
6603
6024
  var variable_features = {};
6604
- var api_host = config['api_host'];
6605
- var is_custom_api = !!api_host && !api_host.match(/\.mixpanel\.com$/);
6606
- if (!('batch_requests' in config) && !is_custom_api && determine_eligibility(token, 'batch', 60)) {
6607
- variable_features['batch_requests'] = true;
6025
+
6026
+ // default to JSON payload for standard mixpanel.com API hosts
6027
+ if (!('api_payload_format' in config)) {
6028
+ var api_host = config['api_host'] || DEFAULT_CONFIG['api_host'];
6029
+ if (api_host.match(/\.mixpanel\.com$/)) {
6030
+ variable_features['api_payload_format'] = PAYLOAD_TYPE_JSON;
6031
+ }
6608
6032
  }
6609
6033
 
6610
6034
  this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
@@ -6629,19 +6053,36 @@
6629
6053
  if (this._batch_requests) {
6630
6054
  if (!_.localStorage.is_supported(true) || !USE_XHR) {
6631
6055
  this._batch_requests = false;
6632
- console$1.log('Turning off Mixpanel request-queueing; needs XHR and localStorage support');
6056
+ console.log('Turning off Mixpanel request-queueing; needs XHR and localStorage support');
6633
6057
  } else {
6634
6058
  this.init_batchers();
6635
6059
  if (sendBeacon && window$1.addEventListener) {
6636
- window$1.addEventListener('unload', _.bind(function() {
6637
- // Before page closes, attempt to flush any events queued up via navigator.sendBeacon.
6638
- // Since sendBeacon doesn't report success/failure, events will not be removed from
6639
- // the persistent store; if the site is loaded again, the events will be flushed again
6640
- // on startup and deduplicated on the Mixpanel server side.
6060
+ // Before page closes or hides (user tabs away etc), attempt to flush any events
6061
+ // queued up via navigator.sendBeacon. Since sendBeacon doesn't report success/failure,
6062
+ // events will not be removed from the persistent store; if the site is loaded again,
6063
+ // the events will be flushed again on startup and deduplicated on the Mixpanel server
6064
+ // side.
6065
+ // There is no reliable way to capture only page close events, so we lean on the
6066
+ // visibilitychange and pagehide events as recommended at
6067
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes.
6068
+ // These events fire when the user clicks away from the current page/tab, so will occur
6069
+ // more frequently than page unload, but are the only mechanism currently for capturing
6070
+ // this scenario somewhat reliably.
6071
+ var flush_on_unload = _.bind(function() {
6641
6072
  if (!this.request_batchers.events.stopped) {
6642
6073
  this.request_batchers.events.flush({unloading: true});
6643
6074
  }
6644
- }, this));
6075
+ }, this);
6076
+ window$1.addEventListener('pagehide', function(ev) {
6077
+ if (ev['persisted']) {
6078
+ flush_on_unload();
6079
+ }
6080
+ });
6081
+ window$1.addEventListener('visibilitychange', function() {
6082
+ if (document$1['visibilityState'] === 'hidden') {
6083
+ flush_on_unload();
6084
+ }
6085
+ });
6645
6086
  }
6646
6087
  }
6647
6088
  }
@@ -6697,7 +6138,7 @@
6697
6138
 
6698
6139
  MixpanelLib.prototype._track_dom = function(DomClass, args) {
6699
6140
  if (this.get_config('img')) {
6700
- console$1.error('You can\'t use DOM tracking functions with img = true.');
6141
+ console.error('You can\'t use DOM tracking functions with img = true.');
6701
6142
  return false;
6702
6143
  }
6703
6144
 
@@ -6807,7 +6248,7 @@
6807
6248
  try {
6808
6249
  succeeded = sendBeacon(url, body_data);
6809
6250
  } catch (e) {
6810
- console$1.error(e);
6251
+ console.error(e);
6811
6252
  succeeded = false;
6812
6253
  }
6813
6254
  try {
@@ -6815,7 +6256,7 @@
6815
6256
  callback(succeeded ? 1 : 0);
6816
6257
  }
6817
6258
  } catch (e) {
6818
- console$1.error(e);
6259
+ console.error(e);
6819
6260
  }
6820
6261
  } else if (USE_XHR) {
6821
6262
  try {
@@ -6847,7 +6288,7 @@
6847
6288
  try {
6848
6289
  response = _.JSONDecode(req.responseText);
6849
6290
  } catch (e) {
6850
- console$1.error(e);
6291
+ console.error(e);
6851
6292
  if (options.ignore_json_errors) {
6852
6293
  response = req.responseText;
6853
6294
  } else {
@@ -6870,7 +6311,7 @@
6870
6311
  } else {
6871
6312
  error = 'Bad HTTP status: ' + req.status + ' ' + req.statusText;
6872
6313
  }
6873
- console$1.error(error);
6314
+ console.error(error);
6874
6315
  if (callback) {
6875
6316
  if (verbose_mode) {
6876
6317
  callback({status: 0, error: error, xhr_req: req});
@@ -6883,7 +6324,7 @@
6883
6324
  };
6884
6325
  req.send(body_data);
6885
6326
  } catch (e) {
6886
- console$1.error(e);
6327
+ console.error(e);
6887
6328
  succeeded = false;
6888
6329
  }
6889
6330
  } else {
@@ -6966,7 +6407,7 @@
6966
6407
  sendRequestFunc: _.bind(function(data, options, cb) {
6967
6408
  this._send_request(
6968
6409
  this.get_config('api_host') + attrs.endpoint,
6969
- encode_data_for_request(data),
6410
+ this._encode_data_for_request(data),
6970
6411
  options,
6971
6412
  this._prepare_callback(cb, data)
6972
6413
  );
@@ -7040,6 +6481,14 @@
7040
6481
  }
7041
6482
  };
7042
6483
 
6484
+ MixpanelLib.prototype._encode_data_for_request = function(data) {
6485
+ var encoded_data = _.JSONEncode(data);
6486
+ if (this.get_config('api_payload_format') === PAYLOAD_TYPE_BASE64) {
6487
+ encoded_data = _.base64Encode(encoded_data);
6488
+ }
6489
+ return {'data': encoded_data};
6490
+ };
6491
+
7043
6492
  // internal method for handling track vs batch-enqueue logic
7044
6493
  MixpanelLib.prototype._track_or_batch = function(options, callback) {
7045
6494
  var truncated_data = _.truncate(options.data, 255);
@@ -7055,11 +6504,11 @@
7055
6504
  truncated_data = this._run_hook('before_send_' + options.type, truncated_data);
7056
6505
  }
7057
6506
  if (truncated_data) {
7058
- console$1.log('MIXPANEL REQUEST:');
7059
- console$1.log(truncated_data);
6507
+ console.log('MIXPANEL REQUEST:');
6508
+ console.log(truncated_data);
7060
6509
  return this._send_request(
7061
6510
  endpoint,
7062
- encode_data_for_request(truncated_data),
6511
+ this._encode_data_for_request(truncated_data),
7063
6512
  send_request_options,
7064
6513
  this._prepare_callback(callback, truncated_data)
7065
6514
  );
@@ -7122,7 +6571,7 @@
7122
6571
  }
7123
6572
 
7124
6573
  if (_.isUndefined(event_name)) {
7125
- console$1.error('No event name provided to mixpanel.track');
6574
+ console.error('No event name provided to mixpanel.track');
7126
6575
  return;
7127
6576
  }
7128
6577
 
@@ -7163,7 +6612,7 @@
7163
6612
  delete properties[blacklisted_prop];
7164
6613
  });
7165
6614
  } else {
7166
- console$1.error('Invalid value for property_blacklist config: ' + property_blacklist);
6615
+ console.error('Invalid value for property_blacklist config: ' + property_blacklist);
7167
6616
  }
7168
6617
 
7169
6618
  var data = {
@@ -7408,7 +6857,7 @@
7408
6857
  */
7409
6858
  MixpanelLib.prototype.time_event = function(event_name) {
7410
6859
  if (_.isUndefined(event_name)) {
7411
- console$1.error('No event name provided to mixpanel.time_event');
6860
+ console.error('No event name provided to mixpanel.time_event');
7412
6861
  return;
7413
6862
  }
7414
6863
 
@@ -7681,7 +7130,7 @@
7681
7130
  // mixpanel.people.identify() call made for this user. It is VERY BAD to make an alias with
7682
7131
  // this ID, as it will duplicate users.
7683
7132
  if (alias === this.get_property(PEOPLE_DISTINCT_ID_KEY)) {
7684
- console$1.critical('Attempting to create alias for existing People user - aborting.');
7133
+ console.critical('Attempting to create alias for existing People user - aborting.');
7685
7134
  return -2;
7686
7135
  }
7687
7136
 
@@ -7701,7 +7150,7 @@
7701
7150
  _this.identify(alias);
7702
7151
  });
7703
7152
  } else {
7704
- console$1.error('alias matches current distinct_id - skipping api call.');
7153
+ console.error('alias matches current distinct_id - skipping api call.');
7705
7154
  this.identify(alias);
7706
7155
  return -1;
7707
7156
  }
@@ -7884,7 +7333,7 @@
7884
7333
  MixpanelLib.prototype._run_hook = function(hook_name) {
7885
7334
  var ret = (this['config']['hooks'][hook_name] || IDENTITY_FUNC).apply(this, slice.call(arguments, 1));
7886
7335
  if (typeof ret === 'undefined') {
7887
- console$1.error(hook_name + ' hook did not return a value');
7336
+ console.error(hook_name + ' hook did not return a value');
7888
7337
  ret = null;
7889
7338
  }
7890
7339
  return ret;
@@ -7950,7 +7399,7 @@
7950
7399
  return;
7951
7400
  }
7952
7401
 
7953
- console$1.log('MIXPANEL NOTIFICATION CHECK');
7402
+ console.log('MIXPANEL NOTIFICATION CHECK');
7954
7403
 
7955
7404
  var data = {
7956
7405
  'verbose': true,
@@ -8396,18 +7845,18 @@
8396
7845
  // Initialization
8397
7846
  if (_.isUndefined(mixpanel_master)) {
8398
7847
  // mixpanel wasn't initialized properly, report error and quit
8399
- console$1.critical('"mixpanel" object not initialized. Ensure you are using the latest version of the Mixpanel JS Library along with the snippet we provide.');
7848
+ console.critical('"mixpanel" object not initialized. Ensure you are using the latest version of the Mixpanel JS Library along with the snippet we provide.');
8400
7849
  return;
8401
7850
  }
8402
7851
  if (mixpanel_master['__loaded'] || (mixpanel_master['config'] && mixpanel_master['persistence'])) {
8403
7852
  // lib has already been loaded at least once; we don't want to override the global object this time so bomb early
8404
- console$1.critical('The Mixpanel library has already been downloaded at least once. Ensure that the Mixpanel code snippet only appears once on the page (and is not double-loaded by a tag manager) in order to avoid errors.');
7853
+ console.critical('The Mixpanel library has already been downloaded at least once. Ensure that the Mixpanel code snippet only appears once on the page (and is not double-loaded by a tag manager) in order to avoid errors.');
8405
7854
  return;
8406
7855
  }
8407
7856
  var snippet_version = mixpanel_master['__SV'] || 0;
8408
7857
  if (snippet_version < 1.1) {
8409
7858
  // mixpanel wasn't initialized properly, report error and quit
8410
- console$1.critical('Version mismatch; please ensure you\'re using the latest version of the Mixpanel code snippet.');
7859
+ console.critical('Version mismatch; please ensure you\'re using the latest version of the Mixpanel code snippet.');
8411
7860
  return;
8412
7861
  }
8413
7862