mixpanel-browser 2.40.0 → 2.42.1
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.
- package/.github/workflows/tests.yml +25 -0
- package/CHANGELOG.md +17 -0
- package/README.md +4 -2
- package/dist/mixpanel-jslib-snippet.min.js +3 -4
- package/dist/mixpanel-jslib-snippet.min.test.js +3 -4
- package/dist/mixpanel.amd.js +122 -642
- package/dist/mixpanel.cjs.js +122 -642
- package/dist/mixpanel.globals.js +125 -645
- package/dist/mixpanel.min.js +149 -160
- package/dist/mixpanel.umd.js +122 -642
- package/doc/build-docs.js +16 -0
- package/doc/readme.io/javascript-full-api-reference.md +18 -0
- package/mixpanel-jslib-snippet.js +0 -19
- package/package.json +3 -3
- package/src/config.js +1 -1
- package/src/gdpr-utils.js +7 -2
- package/src/mixpanel-core.js +27 -37
- package/src/mixpanel-group.js +4 -0
- package/src/request-batcher.js +2 -2
- package/src/utils.js +45 -33
- package/tunnel.log +0 -0
- package/.travis.yml +0 -6
- package/src/autotrack-utils.js +0 -192
- package/src/autotrack.js +0 -355
package/dist/mixpanel.globals.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
var Config = {
|
|
5
5
|
DEBUG: false,
|
|
6
|
-
LIB_VERSION: '2.
|
|
6
|
+
LIB_VERSION: '2.42.1'
|
|
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
|
|
59
|
+
var console = {
|
|
60
60
|
/** @type {function(...*)} */
|
|
61
61
|
log: function() {
|
|
62
62
|
if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
|
|
@@ -70,6 +70,19 @@
|
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
72
|
/** @type {function(...*)} */
|
|
73
|
+
warn: function() {
|
|
74
|
+
if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
|
|
75
|
+
var args = ['Mixpanel warning:'].concat(_.toArray(arguments));
|
|
76
|
+
try {
|
|
77
|
+
windowConsole.warn.apply(windowConsole, args);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
_.each(args, function(arg) {
|
|
80
|
+
windowConsole.warn(arg);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
/** @type {function(...*)} */
|
|
73
86
|
error: function() {
|
|
74
87
|
if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
|
|
75
88
|
var args = ['Mixpanel error:'].concat(_.toArray(arguments));
|
|
@@ -100,14 +113,14 @@
|
|
|
100
113
|
var log_func_with_prefix = function(func, prefix) {
|
|
101
114
|
return function() {
|
|
102
115
|
arguments[0] = '[' + prefix + '] ' + arguments[0];
|
|
103
|
-
return func.apply(console
|
|
116
|
+
return func.apply(console, arguments);
|
|
104
117
|
};
|
|
105
118
|
};
|
|
106
119
|
var console_with_prefix = function(prefix) {
|
|
107
120
|
return {
|
|
108
|
-
log: log_func_with_prefix(console
|
|
109
|
-
error: log_func_with_prefix(console
|
|
110
|
-
critical: log_func_with_prefix(console
|
|
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)
|
|
111
124
|
};
|
|
112
125
|
};
|
|
113
126
|
|
|
@@ -269,10 +282,6 @@
|
|
|
269
282
|
return results;
|
|
270
283
|
};
|
|
271
284
|
|
|
272
|
-
_.identity = function(value) {
|
|
273
|
-
return value;
|
|
274
|
-
};
|
|
275
|
-
|
|
276
285
|
_.include = function(obj, target) {
|
|
277
286
|
var found = false;
|
|
278
287
|
if (obj === null) {
|
|
@@ -373,9 +382,9 @@
|
|
|
373
382
|
try {
|
|
374
383
|
return f.apply(this, arguments);
|
|
375
384
|
} catch (e) {
|
|
376
|
-
console
|
|
385
|
+
console.critical('Implementation error. Please turn on debug and contact support@mixpanel.com.');
|
|
377
386
|
if (Config.DEBUG){
|
|
378
|
-
console
|
|
387
|
+
console.critical(e);
|
|
379
388
|
}
|
|
380
389
|
}
|
|
381
390
|
};
|
|
@@ -935,9 +944,39 @@
|
|
|
935
944
|
// _.isBlockedUA()
|
|
936
945
|
// This is to block various web spiders from executing our JS and
|
|
937
946
|
// sending false tracking data
|
|
947
|
+
var BLOCKED_UA_STRS = [
|
|
948
|
+
'ahrefsbot',
|
|
949
|
+
'baiduspider',
|
|
950
|
+
'bingbot',
|
|
951
|
+
'bingpreview',
|
|
952
|
+
'facebookexternal',
|
|
953
|
+
'petalbot',
|
|
954
|
+
'pinterest',
|
|
955
|
+
'screaming frog',
|
|
956
|
+
'yahoo! slurp',
|
|
957
|
+
'yandexbot',
|
|
958
|
+
|
|
959
|
+
// a whole bunch of goog-specific crawlers
|
|
960
|
+
// https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers
|
|
961
|
+
'adsbot-google',
|
|
962
|
+
'apis-google',
|
|
963
|
+
'duplexweb-google',
|
|
964
|
+
'feedfetcher-google',
|
|
965
|
+
'google favicon',
|
|
966
|
+
'google web preview',
|
|
967
|
+
'google-read-aloud',
|
|
968
|
+
'googlebot',
|
|
969
|
+
'googleweblight',
|
|
970
|
+
'mediapartners-google',
|
|
971
|
+
'storebot-google'
|
|
972
|
+
];
|
|
938
973
|
_.isBlockedUA = function(ua) {
|
|
939
|
-
|
|
940
|
-
|
|
974
|
+
var i;
|
|
975
|
+
ua = ua.toLowerCase();
|
|
976
|
+
for (i = 0; i < BLOCKED_UA_STRS.length; i++) {
|
|
977
|
+
if (ua.indexOf(BLOCKED_UA_STRS[i]) !== -1) {
|
|
978
|
+
return true;
|
|
979
|
+
}
|
|
941
980
|
}
|
|
942
981
|
return false;
|
|
943
982
|
};
|
|
@@ -976,16 +1015,12 @@
|
|
|
976
1015
|
try {
|
|
977
1016
|
result = decodeURIComponent(result);
|
|
978
1017
|
} catch(err) {
|
|
979
|
-
console
|
|
1018
|
+
console.error('Skipping decoding for malformed query param: ' + result);
|
|
980
1019
|
}
|
|
981
1020
|
return result.replace(/\+/g, ' ');
|
|
982
1021
|
}
|
|
983
1022
|
};
|
|
984
1023
|
|
|
985
|
-
_.getHashParam = function(hash, param) {
|
|
986
|
-
var matches = hash.match(new RegExp(param + '=([^&]*)'));
|
|
987
|
-
return matches ? matches[1] : null;
|
|
988
|
-
};
|
|
989
1024
|
|
|
990
1025
|
// _.cookie
|
|
991
1026
|
// Methods partially borrowed from quirksmode.org/js/cookies.html
|
|
@@ -1107,13 +1142,13 @@
|
|
|
1107
1142
|
is_supported: function(force_check) {
|
|
1108
1143
|
var supported = localStorageSupported(null, force_check);
|
|
1109
1144
|
if (!supported) {
|
|
1110
|
-
console
|
|
1145
|
+
console.error('localStorage unsupported; falling back to cookie store');
|
|
1111
1146
|
}
|
|
1112
1147
|
return supported;
|
|
1113
1148
|
},
|
|
1114
1149
|
|
|
1115
1150
|
error: function(msg) {
|
|
1116
|
-
console
|
|
1151
|
+
console.error('localStorage error: ' + msg);
|
|
1117
1152
|
},
|
|
1118
1153
|
|
|
1119
1154
|
get: function(name) {
|
|
@@ -1168,7 +1203,7 @@
|
|
|
1168
1203
|
*/
|
|
1169
1204
|
var register_event = function(element, type, handler, oldSchool, useCapture) {
|
|
1170
1205
|
if (!element) {
|
|
1171
|
-
console
|
|
1206
|
+
console.error('No valid element provided to register_event');
|
|
1172
1207
|
return;
|
|
1173
1208
|
}
|
|
1174
1209
|
|
|
@@ -1652,28 +1687,6 @@
|
|
|
1652
1687
|
return maxlen ? guid.substring(0, maxlen) : guid;
|
|
1653
1688
|
};
|
|
1654
1689
|
|
|
1655
|
-
/**
|
|
1656
|
-
* Check deterministically whether to include or exclude from a feature rollout/test based on the
|
|
1657
|
-
* given string and the desired percentage to include.
|
|
1658
|
-
* @param {String} str - string to run the check against (for instance a project's token)
|
|
1659
|
-
* @param {String} feature - name of feature (for inclusion in hash, to ensure different results
|
|
1660
|
-
* for different features)
|
|
1661
|
-
* @param {Number} percent_allowed - percentage chance that a given string will be included
|
|
1662
|
-
* @returns {Boolean} whether the given string should be included
|
|
1663
|
-
*/
|
|
1664
|
-
var determine_eligibility = _.safewrap(function(str, feature, percent_allowed) {
|
|
1665
|
-
str = str + feature;
|
|
1666
|
-
|
|
1667
|
-
// Bernstein's hash: http://www.cse.yorku.ca/~oz/hash.html#djb2
|
|
1668
|
-
var hash = 5381;
|
|
1669
|
-
for (var i = 0; i < str.length; i++) {
|
|
1670
|
-
hash = ((hash << 5) + hash) + str.charCodeAt(i);
|
|
1671
|
-
hash = hash & hash;
|
|
1672
|
-
}
|
|
1673
|
-
var dart = (hash >>> 0) % 100;
|
|
1674
|
-
return dart < percent_allowed;
|
|
1675
|
-
});
|
|
1676
|
-
|
|
1677
1690
|
// naive way to extract domain name (example.com) from full hostname (my.sub.example.com)
|
|
1678
1691
|
var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i;
|
|
1679
1692
|
// this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk
|
|
@@ -1724,539 +1737,6 @@
|
|
|
1724
1737
|
_['info']['browserVersion'] = _.info.browserVersion;
|
|
1725
1738
|
_['info']['properties'] = _.info.properties;
|
|
1726
1739
|
|
|
1727
|
-
/*
|
|
1728
|
-
* Get the className of an element, accounting for edge cases where element.className is an object
|
|
1729
|
-
* @param {Element} el - element to get the className of
|
|
1730
|
-
* @returns {string} the element's class
|
|
1731
|
-
*/
|
|
1732
|
-
function getClassName(el) {
|
|
1733
|
-
switch(typeof el.className) {
|
|
1734
|
-
case 'string':
|
|
1735
|
-
return el.className;
|
|
1736
|
-
case 'object': // handle cases where className might be SVGAnimatedString or some other type
|
|
1737
|
-
return el.className.baseVal || el.getAttribute('class') || '';
|
|
1738
|
-
default: // future proof
|
|
1739
|
-
return '';
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
/*
|
|
1744
|
-
* Get the direct text content of an element, protecting against sensitive data collection.
|
|
1745
|
-
* Concats textContent of each of the element's text node children; this avoids potential
|
|
1746
|
-
* collection of sensitive data that could happen if we used element.textContent and the
|
|
1747
|
-
* element had sensitive child elements, since element.textContent includes child content.
|
|
1748
|
-
* Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
|
|
1749
|
-
* @param {Element} el - element to get the text of
|
|
1750
|
-
* @returns {string} the element's direct text content
|
|
1751
|
-
*/
|
|
1752
|
-
function getSafeText(el) {
|
|
1753
|
-
var elText = '';
|
|
1754
|
-
|
|
1755
|
-
if (shouldTrackElement(el) && el.childNodes && el.childNodes.length) {
|
|
1756
|
-
_.each(el.childNodes, function(child) {
|
|
1757
|
-
if (isTextNode(child) && child.textContent) {
|
|
1758
|
-
elText += _.trim(child.textContent)
|
|
1759
|
-
// scrub potentially sensitive values
|
|
1760
|
-
.split(/(\s+)/).filter(shouldTrackValue).join('')
|
|
1761
|
-
// normalize whitespace
|
|
1762
|
-
.replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ')
|
|
1763
|
-
// truncate
|
|
1764
|
-
.substring(0, 255);
|
|
1765
|
-
}
|
|
1766
|
-
});
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
return _.trim(elText);
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
/*
|
|
1773
|
-
* Check whether an element has nodeType Node.ELEMENT_NODE
|
|
1774
|
-
* @param {Element} el - element to check
|
|
1775
|
-
* @returns {boolean} whether el is of the correct nodeType
|
|
1776
|
-
*/
|
|
1777
|
-
function isElementNode(el) {
|
|
1778
|
-
return el && el.nodeType === 1; // Node.ELEMENT_NODE - use integer constant for browser portability
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
|
-
/*
|
|
1782
|
-
* Check whether an element is of a given tag type.
|
|
1783
|
-
* Due to potential reference discrepancies (such as the webcomponents.js polyfill),
|
|
1784
|
-
* we want to match tagNames instead of specific references because something like
|
|
1785
|
-
* element === document.body won't always work because element might not be a native
|
|
1786
|
-
* element.
|
|
1787
|
-
* @param {Element} el - element to check
|
|
1788
|
-
* @param {string} tag - tag name (e.g., "div")
|
|
1789
|
-
* @returns {boolean} whether el is of the given tag type
|
|
1790
|
-
*/
|
|
1791
|
-
function isTag(el, tag) {
|
|
1792
|
-
return el && el.tagName && el.tagName.toLowerCase() === tag.toLowerCase();
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
/*
|
|
1796
|
-
* Check whether an element has nodeType Node.TEXT_NODE
|
|
1797
|
-
* @param {Element} el - element to check
|
|
1798
|
-
* @returns {boolean} whether el is of the correct nodeType
|
|
1799
|
-
*/
|
|
1800
|
-
function isTextNode(el) {
|
|
1801
|
-
return el && el.nodeType === 3; // Node.TEXT_NODE - use integer constant for browser portability
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
/*
|
|
1805
|
-
* Check whether a DOM event should be "tracked" or if it may contain sentitive data
|
|
1806
|
-
* using a variety of heuristics.
|
|
1807
|
-
* @param {Element} el - element to check
|
|
1808
|
-
* @param {Event} event - event to check
|
|
1809
|
-
* @returns {boolean} whether the event should be tracked
|
|
1810
|
-
*/
|
|
1811
|
-
function shouldTrackDomEvent(el, event) {
|
|
1812
|
-
if (!el || isTag(el, 'html') || !isElementNode(el)) {
|
|
1813
|
-
return false;
|
|
1814
|
-
}
|
|
1815
|
-
var tag = el.tagName.toLowerCase();
|
|
1816
|
-
switch (tag) {
|
|
1817
|
-
case 'html':
|
|
1818
|
-
return false;
|
|
1819
|
-
case 'form':
|
|
1820
|
-
return event.type === 'submit';
|
|
1821
|
-
case 'input':
|
|
1822
|
-
if (['button', 'submit'].indexOf(el.getAttribute('type')) === -1) {
|
|
1823
|
-
return event.type === 'change';
|
|
1824
|
-
} else {
|
|
1825
|
-
return event.type === 'click';
|
|
1826
|
-
}
|
|
1827
|
-
case 'select':
|
|
1828
|
-
case 'textarea':
|
|
1829
|
-
return event.type === 'change';
|
|
1830
|
-
default:
|
|
1831
|
-
return event.type === 'click';
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
/*
|
|
1836
|
-
* Check whether a DOM element should be "tracked" or if it may contain sentitive data
|
|
1837
|
-
* using a variety of heuristics.
|
|
1838
|
-
* @param {Element} el - element to check
|
|
1839
|
-
* @returns {boolean} whether the element should be tracked
|
|
1840
|
-
*/
|
|
1841
|
-
function shouldTrackElement(el) {
|
|
1842
|
-
for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
|
|
1843
|
-
var classes = getClassName(curEl).split(' ');
|
|
1844
|
-
if (_.includes(classes, 'mp-sensitive') || _.includes(classes, 'mp-no-track')) {
|
|
1845
|
-
return false;
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
if (_.includes(getClassName(el).split(' '), 'mp-include')) {
|
|
1850
|
-
return true;
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
// don't send data from inputs or similar elements since there will always be
|
|
1854
|
-
// a risk of clientside javascript placing sensitive data in attributes
|
|
1855
|
-
if (
|
|
1856
|
-
isTag(el, 'input') ||
|
|
1857
|
-
isTag(el, 'select') ||
|
|
1858
|
-
isTag(el, 'textarea') ||
|
|
1859
|
-
el.getAttribute('contenteditable') === 'true'
|
|
1860
|
-
) {
|
|
1861
|
-
return false;
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
// don't include hidden or password fields
|
|
1865
|
-
var type = el.type || '';
|
|
1866
|
-
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"]
|
|
1867
|
-
switch(type.toLowerCase()) {
|
|
1868
|
-
case 'hidden':
|
|
1869
|
-
return false;
|
|
1870
|
-
case 'password':
|
|
1871
|
-
return false;
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
// filter out data from fields that look like sensitive fields
|
|
1876
|
-
var name = el.name || el.id || '';
|
|
1877
|
-
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"]
|
|
1878
|
-
var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
|
|
1879
|
-
if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
|
|
1880
|
-
return false;
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
return true;
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
/*
|
|
1888
|
-
* Check whether a string value should be "tracked" or if it may contain sentitive data
|
|
1889
|
-
* using a variety of heuristics.
|
|
1890
|
-
* @param {string} value - string value to check
|
|
1891
|
-
* @returns {boolean} whether the element should be tracked
|
|
1892
|
-
*/
|
|
1893
|
-
function shouldTrackValue(value) {
|
|
1894
|
-
if (value === null || _.isUndefined(value)) {
|
|
1895
|
-
return false;
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
if (typeof value === 'string') {
|
|
1899
|
-
value = _.trim(value);
|
|
1900
|
-
|
|
1901
|
-
// check to see if input value looks like a credit card number
|
|
1902
|
-
// see: https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s20.html
|
|
1903
|
-
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}))$/;
|
|
1904
|
-
if (ccRegex.test((value || '').replace(/[- ]/g, ''))) {
|
|
1905
|
-
return false;
|
|
1906
|
-
}
|
|
1907
|
-
|
|
1908
|
-
// check to see if input value looks like a social security number
|
|
1909
|
-
var ssnRegex = /(^\d{3}-?\d{2}-?\d{4}$)/;
|
|
1910
|
-
if (ssnRegex.test(value)) {
|
|
1911
|
-
return false;
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
return true;
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
var autotrack = {
|
|
1919
|
-
_initializedTokens: [],
|
|
1920
|
-
|
|
1921
|
-
_previousElementSibling: function(el) {
|
|
1922
|
-
if (el.previousElementSibling) {
|
|
1923
|
-
return el.previousElementSibling;
|
|
1924
|
-
} else {
|
|
1925
|
-
do {
|
|
1926
|
-
el = el.previousSibling;
|
|
1927
|
-
} while (el && !isElementNode(el));
|
|
1928
|
-
return el;
|
|
1929
|
-
}
|
|
1930
|
-
},
|
|
1931
|
-
|
|
1932
|
-
_loadScript: function(scriptUrlToLoad, callback) {
|
|
1933
|
-
var scriptTag = document.createElement('script');
|
|
1934
|
-
scriptTag.type = 'text/javascript';
|
|
1935
|
-
scriptTag.src = scriptUrlToLoad;
|
|
1936
|
-
scriptTag.onload = callback;
|
|
1937
|
-
|
|
1938
|
-
var scripts = document.getElementsByTagName('script');
|
|
1939
|
-
if (scripts.length > 0) {
|
|
1940
|
-
scripts[0].parentNode.insertBefore(scriptTag, scripts[0]);
|
|
1941
|
-
} else {
|
|
1942
|
-
document.body.appendChild(scriptTag);
|
|
1943
|
-
}
|
|
1944
|
-
},
|
|
1945
|
-
|
|
1946
|
-
_getPropertiesFromElement: function(elem) {
|
|
1947
|
-
var props = {
|
|
1948
|
-
'classes': getClassName(elem).split(' '),
|
|
1949
|
-
'tag_name': elem.tagName.toLowerCase()
|
|
1950
|
-
};
|
|
1951
|
-
|
|
1952
|
-
if (shouldTrackElement(elem)) {
|
|
1953
|
-
_.each(elem.attributes, function(attr) {
|
|
1954
|
-
if (shouldTrackValue(attr.value)) {
|
|
1955
|
-
props['attr__' + attr.name] = attr.value;
|
|
1956
|
-
}
|
|
1957
|
-
});
|
|
1958
|
-
}
|
|
1959
|
-
|
|
1960
|
-
var nthChild = 1;
|
|
1961
|
-
var nthOfType = 1;
|
|
1962
|
-
var currentElem = elem;
|
|
1963
|
-
while (currentElem = this._previousElementSibling(currentElem)) { // eslint-disable-line no-cond-assign
|
|
1964
|
-
nthChild++;
|
|
1965
|
-
if (currentElem.tagName === elem.tagName) {
|
|
1966
|
-
nthOfType++;
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
props['nth_child'] = nthChild;
|
|
1970
|
-
props['nth_of_type'] = nthOfType;
|
|
1971
|
-
|
|
1972
|
-
return props;
|
|
1973
|
-
},
|
|
1974
|
-
|
|
1975
|
-
_getDefaultProperties: function(eventType) {
|
|
1976
|
-
return {
|
|
1977
|
-
'$event_type': eventType,
|
|
1978
|
-
'$ce_version': 1,
|
|
1979
|
-
'$host': window.location.host,
|
|
1980
|
-
'$pathname': window.location.pathname
|
|
1981
|
-
};
|
|
1982
|
-
},
|
|
1983
|
-
|
|
1984
|
-
_extractCustomPropertyValue: function(customProperty) {
|
|
1985
|
-
var propValues = [];
|
|
1986
|
-
_.each(document.querySelectorAll(customProperty['css_selector']), function(matchedElem) {
|
|
1987
|
-
var value;
|
|
1988
|
-
|
|
1989
|
-
if (['input', 'select'].indexOf(matchedElem.tagName.toLowerCase()) > -1) {
|
|
1990
|
-
value = matchedElem['value'];
|
|
1991
|
-
} else if (matchedElem['textContent']) {
|
|
1992
|
-
value = matchedElem['textContent'];
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
if (shouldTrackValue(value)) {
|
|
1996
|
-
propValues.push(value);
|
|
1997
|
-
}
|
|
1998
|
-
});
|
|
1999
|
-
return propValues.join(', ');
|
|
2000
|
-
},
|
|
2001
|
-
|
|
2002
|
-
_getCustomProperties: function(targetElementList) {
|
|
2003
|
-
var props = {};
|
|
2004
|
-
_.each(this._customProperties, function(customProperty) {
|
|
2005
|
-
_.each(customProperty['event_selectors'], function(eventSelector) {
|
|
2006
|
-
var eventElements = document.querySelectorAll(eventSelector);
|
|
2007
|
-
_.each(eventElements, function(eventElement) {
|
|
2008
|
-
if (_.includes(targetElementList, eventElement) && shouldTrackElement(eventElement)) {
|
|
2009
|
-
props[customProperty['name']] = this._extractCustomPropertyValue(customProperty);
|
|
2010
|
-
}
|
|
2011
|
-
}, this);
|
|
2012
|
-
}, this);
|
|
2013
|
-
}, this);
|
|
2014
|
-
return props;
|
|
2015
|
-
},
|
|
2016
|
-
|
|
2017
|
-
_getEventTarget: function(e) {
|
|
2018
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/Event/target#Compatibility_notes
|
|
2019
|
-
if (typeof e.target === 'undefined') {
|
|
2020
|
-
return e.srcElement;
|
|
2021
|
-
} else {
|
|
2022
|
-
return e.target;
|
|
2023
|
-
}
|
|
2024
|
-
},
|
|
2025
|
-
|
|
2026
|
-
_trackEvent: function(e, instance) {
|
|
2027
|
-
/*** Don't mess with this code without running IE8 tests on it ***/
|
|
2028
|
-
var target = this._getEventTarget(e);
|
|
2029
|
-
if (isTextNode(target)) { // defeat Safari bug (see: http://www.quirksmode.org/js/events_properties.html)
|
|
2030
|
-
target = target.parentNode;
|
|
2031
|
-
}
|
|
2032
|
-
|
|
2033
|
-
if (shouldTrackDomEvent(target, e)) {
|
|
2034
|
-
var targetElementList = [target];
|
|
2035
|
-
var curEl = target;
|
|
2036
|
-
while (curEl.parentNode && !isTag(curEl, 'body')) {
|
|
2037
|
-
targetElementList.push(curEl.parentNode);
|
|
2038
|
-
curEl = curEl.parentNode;
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
var elementsJson = [];
|
|
2042
|
-
var href, explicitNoTrack = false;
|
|
2043
|
-
_.each(targetElementList, function(el) {
|
|
2044
|
-
var shouldTrackEl = shouldTrackElement(el);
|
|
2045
|
-
|
|
2046
|
-
// if the element or a parent element is an anchor tag
|
|
2047
|
-
// include the href as a property
|
|
2048
|
-
if (el.tagName.toLowerCase() === 'a') {
|
|
2049
|
-
href = el.getAttribute('href');
|
|
2050
|
-
href = shouldTrackEl && shouldTrackValue(href) && href;
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2053
|
-
// allow users to programatically prevent tracking of elements by adding class 'mp-no-track'
|
|
2054
|
-
var classes = getClassName(el).split(' ');
|
|
2055
|
-
if (_.includes(classes, 'mp-no-track')) {
|
|
2056
|
-
explicitNoTrack = true;
|
|
2057
|
-
}
|
|
2058
|
-
|
|
2059
|
-
elementsJson.push(this._getPropertiesFromElement(el));
|
|
2060
|
-
}, this);
|
|
2061
|
-
|
|
2062
|
-
if (explicitNoTrack) {
|
|
2063
|
-
return false;
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
|
-
// only populate text content from target element (not parents)
|
|
2067
|
-
// to prevent text within a sensitive element from being collected
|
|
2068
|
-
// as part of a parent's el.textContent
|
|
2069
|
-
var elementText;
|
|
2070
|
-
var safeElementText = getSafeText(target);
|
|
2071
|
-
if (safeElementText && safeElementText.length) {
|
|
2072
|
-
elementText = safeElementText;
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
var props = _.extend(
|
|
2076
|
-
this._getDefaultProperties(e.type),
|
|
2077
|
-
{
|
|
2078
|
-
'$elements': elementsJson,
|
|
2079
|
-
'$el_attr__href': href,
|
|
2080
|
-
'$el_text': elementText
|
|
2081
|
-
},
|
|
2082
|
-
this._getCustomProperties(targetElementList)
|
|
2083
|
-
);
|
|
2084
|
-
|
|
2085
|
-
instance.track('$web_event', props);
|
|
2086
|
-
return true;
|
|
2087
|
-
}
|
|
2088
|
-
},
|
|
2089
|
-
|
|
2090
|
-
// only reason is to stub for unit tests
|
|
2091
|
-
// since you can't override window.location props
|
|
2092
|
-
_navigate: function(href) {
|
|
2093
|
-
window.location.href = href;
|
|
2094
|
-
},
|
|
2095
|
-
|
|
2096
|
-
_addDomEventHandlers: function(instance) {
|
|
2097
|
-
var handler = _.bind(function(e) {
|
|
2098
|
-
e = e || window.event;
|
|
2099
|
-
this._trackEvent(e, instance);
|
|
2100
|
-
}, this);
|
|
2101
|
-
_.register_event(document, 'submit', handler, false, true);
|
|
2102
|
-
_.register_event(document, 'change', handler, false, true);
|
|
2103
|
-
_.register_event(document, 'click', handler, false, true);
|
|
2104
|
-
},
|
|
2105
|
-
|
|
2106
|
-
_customProperties: {},
|
|
2107
|
-
init: function(instance) {
|
|
2108
|
-
if (!(document && document.body)) {
|
|
2109
|
-
console.log('document not ready yet, trying again in 500 milliseconds...');
|
|
2110
|
-
var that = this;
|
|
2111
|
-
setTimeout(function() { that.init(instance); }, 500);
|
|
2112
|
-
return;
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2115
|
-
var token = instance.get_config('token');
|
|
2116
|
-
if (this._initializedTokens.indexOf(token) > -1) {
|
|
2117
|
-
console.log('autotrack already initialized for token "' + token + '"');
|
|
2118
|
-
return;
|
|
2119
|
-
}
|
|
2120
|
-
this._initializedTokens.push(token);
|
|
2121
|
-
|
|
2122
|
-
if (!this._maybeLoadEditor(instance)) { // don't autotrack actions when the editor is enabled
|
|
2123
|
-
var parseDecideResponse = _.bind(function(response) {
|
|
2124
|
-
if (response && response['config'] && response['config']['enable_collect_everything'] === true) {
|
|
2125
|
-
|
|
2126
|
-
if (response['custom_properties']) {
|
|
2127
|
-
this._customProperties = response['custom_properties'];
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
instance.track('$web_event', _.extend({
|
|
2131
|
-
'$title': document.title
|
|
2132
|
-
}, this._getDefaultProperties('pageview')));
|
|
2133
|
-
|
|
2134
|
-
this._addDomEventHandlers(instance);
|
|
2135
|
-
|
|
2136
|
-
} else {
|
|
2137
|
-
instance['__autotrack_enabled'] = false;
|
|
2138
|
-
}
|
|
2139
|
-
}, this);
|
|
2140
|
-
|
|
2141
|
-
instance._send_request(
|
|
2142
|
-
instance.get_config('api_host') + '/decide/', {
|
|
2143
|
-
'verbose': true,
|
|
2144
|
-
'version': '1',
|
|
2145
|
-
'lib': 'web',
|
|
2146
|
-
'token': token
|
|
2147
|
-
},
|
|
2148
|
-
{method: 'GET', transport: 'XHR'},
|
|
2149
|
-
instance._prepare_callback(parseDecideResponse)
|
|
2150
|
-
);
|
|
2151
|
-
}
|
|
2152
|
-
},
|
|
2153
|
-
|
|
2154
|
-
_editorParamsFromHash: function(instance, hash) {
|
|
2155
|
-
var editorParams;
|
|
2156
|
-
try {
|
|
2157
|
-
var state = _.getHashParam(hash, 'state');
|
|
2158
|
-
state = JSON.parse(decodeURIComponent(state));
|
|
2159
|
-
var expiresInSeconds = _.getHashParam(hash, 'expires_in');
|
|
2160
|
-
editorParams = {
|
|
2161
|
-
'accessToken': _.getHashParam(hash, 'access_token'),
|
|
2162
|
-
'accessTokenExpiresAt': (new Date()).getTime() + (Number(expiresInSeconds) * 1000),
|
|
2163
|
-
'bookmarkletMode': !!state['bookmarkletMode'],
|
|
2164
|
-
'projectId': state['projectId'],
|
|
2165
|
-
'projectOwnerId': state['projectOwnerId'],
|
|
2166
|
-
'projectToken': state['token'],
|
|
2167
|
-
'readOnly': state['readOnly'],
|
|
2168
|
-
'userFlags': state['userFlags'],
|
|
2169
|
-
'userId': state['userId']
|
|
2170
|
-
};
|
|
2171
|
-
window.sessionStorage.setItem('editorParams', JSON.stringify(editorParams));
|
|
2172
|
-
|
|
2173
|
-
if (state['desiredHash']) {
|
|
2174
|
-
window.location.hash = state['desiredHash'];
|
|
2175
|
-
} else if (window.history) {
|
|
2176
|
-
history.replaceState('', document.title, window.location.pathname + window.location.search); // completely remove hash
|
|
2177
|
-
} else {
|
|
2178
|
-
window.location.hash = ''; // clear hash (but leaves # unfortunately)
|
|
2179
|
-
}
|
|
2180
|
-
} catch (e) {
|
|
2181
|
-
console.error('Unable to parse data from hash', e);
|
|
2182
|
-
}
|
|
2183
|
-
return editorParams;
|
|
2184
|
-
},
|
|
2185
|
-
|
|
2186
|
-
/**
|
|
2187
|
-
* To load the visual editor, we need an access token and other state. That state comes from one of three places:
|
|
2188
|
-
* 1. In the URL hash params if the customer is using an old snippet
|
|
2189
|
-
* 2. From session storage under the key `_mpcehash` if the snippet already parsed the hash
|
|
2190
|
-
* 3. From session storage under the key `editorParams` if the editor was initialized on a previous page
|
|
2191
|
-
*/
|
|
2192
|
-
_maybeLoadEditor: function(instance) {
|
|
2193
|
-
try {
|
|
2194
|
-
var parseFromUrl = false;
|
|
2195
|
-
if (_.getHashParam(window.location.hash, 'state')) {
|
|
2196
|
-
var state = _.getHashParam(window.location.hash, 'state');
|
|
2197
|
-
state = JSON.parse(decodeURIComponent(state));
|
|
2198
|
-
parseFromUrl = state['action'] === 'mpeditor';
|
|
2199
|
-
}
|
|
2200
|
-
var parseFromStorage = !!window.sessionStorage.getItem('_mpcehash');
|
|
2201
|
-
var editorParams;
|
|
2202
|
-
|
|
2203
|
-
if (parseFromUrl) { // happens if they are initializing the editor using an old snippet
|
|
2204
|
-
editorParams = this._editorParamsFromHash(instance, window.location.hash);
|
|
2205
|
-
} else if (parseFromStorage) { // happens if they are initialized the editor and using the new snippet
|
|
2206
|
-
editorParams = this._editorParamsFromHash(instance, window.sessionStorage.getItem('_mpcehash'));
|
|
2207
|
-
window.sessionStorage.removeItem('_mpcehash');
|
|
2208
|
-
} else { // get credentials from sessionStorage from a previous initialzation
|
|
2209
|
-
editorParams = JSON.parse(window.sessionStorage.getItem('editorParams') || '{}');
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
if (editorParams['projectToken'] && instance.get_config('token') === editorParams['projectToken']) {
|
|
2213
|
-
this._loadEditor(instance, editorParams);
|
|
2214
|
-
return true;
|
|
2215
|
-
} else {
|
|
2216
|
-
return false;
|
|
2217
|
-
}
|
|
2218
|
-
} catch (e) {
|
|
2219
|
-
return false;
|
|
2220
|
-
}
|
|
2221
|
-
},
|
|
2222
|
-
|
|
2223
|
-
_loadEditor: function(instance, editorParams) {
|
|
2224
|
-
if (!window['_mpEditorLoaded']) { // only load the codeless event editor once, even if there are multiple instances of MixpanelLib
|
|
2225
|
-
window['_mpEditorLoaded'] = true;
|
|
2226
|
-
var editorUrl = instance.get_config('app_host')
|
|
2227
|
-
+ '/js-bundle/reports/collect-everything/editor.js?_ts='
|
|
2228
|
-
+ (new Date()).getTime();
|
|
2229
|
-
this._loadScript(editorUrl, function() {
|
|
2230
|
-
window['mp_load_editor'](editorParams);
|
|
2231
|
-
});
|
|
2232
|
-
return true;
|
|
2233
|
-
}
|
|
2234
|
-
return false;
|
|
2235
|
-
},
|
|
2236
|
-
|
|
2237
|
-
// this is a mechanism to ramp up CE with no server-side interaction.
|
|
2238
|
-
// when CE is active, every page load results in a decide request. we
|
|
2239
|
-
// need to gently ramp this up so we don't overload decide. this decides
|
|
2240
|
-
// deterministically if CE is enabled for this project by modding the char
|
|
2241
|
-
// value of the project token.
|
|
2242
|
-
enabledForProject: function(token, numBuckets, numEnabledBuckets) {
|
|
2243
|
-
numBuckets = !_.isUndefined(numBuckets) ? numBuckets : 10;
|
|
2244
|
-
numEnabledBuckets = !_.isUndefined(numEnabledBuckets) ? numEnabledBuckets : 10;
|
|
2245
|
-
var charCodeSum = 0;
|
|
2246
|
-
for (var i = 0; i < token.length; i++) {
|
|
2247
|
-
charCodeSum += token.charCodeAt(i);
|
|
2248
|
-
}
|
|
2249
|
-
return (charCodeSum % numBuckets) < numEnabledBuckets;
|
|
2250
|
-
},
|
|
2251
|
-
|
|
2252
|
-
isBrowserSupported: function() {
|
|
2253
|
-
return _.isFunction(document.querySelectorAll);
|
|
2254
|
-
}
|
|
2255
|
-
};
|
|
2256
|
-
|
|
2257
|
-
_.bind_instance_methods(autotrack);
|
|
2258
|
-
_.safewrap_instance_methods(autotrack);
|
|
2259
|
-
|
|
2260
1740
|
/**
|
|
2261
1741
|
* DomTracker Object
|
|
2262
1742
|
* @constructor
|
|
@@ -2285,7 +1765,7 @@
|
|
|
2285
1765
|
var elements = _.dom_query(query);
|
|
2286
1766
|
|
|
2287
1767
|
if (elements.length === 0) {
|
|
2288
|
-
console
|
|
1768
|
+
console.error('The DOM query (' + query + ') returned 0 elements');
|
|
2289
1769
|
return;
|
|
2290
1770
|
}
|
|
2291
1771
|
|
|
@@ -2945,9 +2425,9 @@
|
|
|
2945
2425
|
} else if (
|
|
2946
2426
|
_.isObject(res) &&
|
|
2947
2427
|
res.xhr_req &&
|
|
2948
|
-
(res.xhr_req['status'] >= 500 || res.xhr_req['status']
|
|
2428
|
+
(res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout')
|
|
2949
2429
|
) {
|
|
2950
|
-
// network or API error, retry
|
|
2430
|
+
// network or API error, or 429 Too Many Requests, retry
|
|
2951
2431
|
var retryMS = this.flushInterval * 2;
|
|
2952
2432
|
var headers = res.xhr_req['responseHeaders'];
|
|
2953
2433
|
if (headers) {
|
|
@@ -3077,9 +2557,14 @@
|
|
|
3077
2557
|
*/
|
|
3078
2558
|
function hasOptedOut(token, options) {
|
|
3079
2559
|
if (_hasDoNotTrackFlagOn(options)) {
|
|
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"');
|
|
3080
2561
|
return true;
|
|
3081
2562
|
}
|
|
3082
|
-
|
|
2563
|
+
var optedOut = _getStorageValue(token, options) === '0';
|
|
2564
|
+
if (optedOut) {
|
|
2565
|
+
console.warn('You are opted out of Mixpanel tracking. This will prevent the Mixpanel SDK from sending any data.');
|
|
2566
|
+
}
|
|
2567
|
+
return optedOut;
|
|
3083
2568
|
}
|
|
3084
2569
|
|
|
3085
2570
|
/**
|
|
@@ -3512,9 +2997,13 @@
|
|
|
3512
2997
|
* Permanently delete a group.
|
|
3513
2998
|
*
|
|
3514
2999
|
* ### Usage:
|
|
3000
|
+
*
|
|
3515
3001
|
* mixpanel.get_group('company', 'mixpanel').delete();
|
|
3002
|
+
*
|
|
3003
|
+
* @param {Function} [callback] If provided, the callback will be called after the tracking event
|
|
3516
3004
|
*/
|
|
3517
3005
|
MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) {
|
|
3006
|
+
// bracket notation above prevents a minification error related to reserved words
|
|
3518
3007
|
var data = this.delete_action();
|
|
3519
3008
|
return this._send_request(data, callback);
|
|
3520
3009
|
});
|
|
@@ -3614,7 +3103,7 @@
|
|
|
3614
3103
|
|
|
3615
3104
|
var storage_type = config['persistence'];
|
|
3616
3105
|
if (storage_type !== 'cookie' && storage_type !== 'localStorage') {
|
|
3617
|
-
console
|
|
3106
|
+
console.critical('Unknown persistence type ' + storage_type + '; falling back to cookie');
|
|
3618
3107
|
storage_type = config['persistence'] = 'cookie';
|
|
3619
3108
|
}
|
|
3620
3109
|
|
|
@@ -3972,8 +3461,8 @@
|
|
|
3972
3461
|
this._pop_from_people_queue(UNSET_ACTION, q_data);
|
|
3973
3462
|
}
|
|
3974
3463
|
|
|
3975
|
-
console
|
|
3976
|
-
console
|
|
3464
|
+
console.log('MIXPANEL PEOPLE REQUEST (QUEUED, PENDING IDENTIFY):');
|
|
3465
|
+
console.log(data);
|
|
3977
3466
|
|
|
3978
3467
|
this.save();
|
|
3979
3468
|
};
|
|
@@ -4016,7 +3505,7 @@
|
|
|
4016
3505
|
} else if (queue === UNION_ACTION) {
|
|
4017
3506
|
return UNION_QUEUE_KEY;
|
|
4018
3507
|
} else {
|
|
4019
|
-
console
|
|
3508
|
+
console.error('Invalid queue:', queue);
|
|
4020
3509
|
}
|
|
4021
3510
|
};
|
|
4022
3511
|
|
|
@@ -5984,7 +5473,7 @@
|
|
|
5984
5473
|
_.each(prop, function(v, k) {
|
|
5985
5474
|
if (!this._is_reserved_property(k)) {
|
|
5986
5475
|
if (isNaN(parseFloat(v))) {
|
|
5987
|
-
console
|
|
5476
|
+
console.error('Invalid increment value passed to mixpanel.people.increment - must be a number');
|
|
5988
5477
|
return;
|
|
5989
5478
|
} else {
|
|
5990
5479
|
$add[k] = v;
|
|
@@ -6108,7 +5597,7 @@
|
|
|
6108
5597
|
if (!_.isNumber(amount)) {
|
|
6109
5598
|
amount = parseFloat(amount);
|
|
6110
5599
|
if (isNaN(amount)) {
|
|
6111
|
-
console
|
|
5600
|
+
console.error('Invalid value passed to mixpanel.people.track_charge - must be a number');
|
|
6112
5601
|
return;
|
|
6113
5602
|
}
|
|
6114
5603
|
}
|
|
@@ -6144,7 +5633,7 @@
|
|
|
6144
5633
|
*/
|
|
6145
5634
|
MixpanelPeople.prototype.delete_user = function() {
|
|
6146
5635
|
if (!this._identify_called()) {
|
|
6147
|
-
console
|
|
5636
|
+
console.error('mixpanel.people.delete_user() requires you to call identify() first');
|
|
6148
5637
|
return;
|
|
6149
5638
|
}
|
|
6150
5639
|
var data = {'$delete': this._mixpanel.get_distinct_id()};
|
|
@@ -6218,7 +5707,7 @@
|
|
|
6218
5707
|
} else if (UNION_ACTION in data) {
|
|
6219
5708
|
this._mixpanel['persistence']._add_to_people_queue(UNION_ACTION, data);
|
|
6220
5709
|
} else {
|
|
6221
|
-
console
|
|
5710
|
+
console.error('Invalid call to _enqueue():', data);
|
|
6222
5711
|
}
|
|
6223
5712
|
};
|
|
6224
5713
|
|
|
@@ -6386,7 +5875,6 @@
|
|
|
6386
5875
|
'api_method': 'POST',
|
|
6387
5876
|
'api_transport': 'XHR',
|
|
6388
5877
|
'app_host': 'https://mixpanel.com',
|
|
6389
|
-
'autotrack': true,
|
|
6390
5878
|
'cdn': 'https://cdn.mxpnl.com',
|
|
6391
5879
|
'cross_site_cookie': false,
|
|
6392
5880
|
'cross_subdomain_cookie': true,
|
|
@@ -6417,7 +5905,7 @@
|
|
|
6417
5905
|
'inapp_protocol': '//',
|
|
6418
5906
|
'inapp_link_new_window': false,
|
|
6419
5907
|
'ignore_dnt': false,
|
|
6420
|
-
'batch_requests':
|
|
5908
|
+
'batch_requests': true,
|
|
6421
5909
|
'batch_size': 50,
|
|
6422
5910
|
'batch_flush_interval_ms': 5000,
|
|
6423
5911
|
'batch_request_timeout_ms': 90000,
|
|
@@ -6450,7 +5938,7 @@
|
|
|
6450
5938
|
instance = target;
|
|
6451
5939
|
} else {
|
|
6452
5940
|
if (target && !_.isArray(target)) {
|
|
6453
|
-
console
|
|
5941
|
+
console.error('You have already initialized ' + name);
|
|
6454
5942
|
return;
|
|
6455
5943
|
}
|
|
6456
5944
|
instance = new MixpanelLib();
|
|
@@ -6469,21 +5957,6 @@
|
|
|
6469
5957
|
// global debug to be true
|
|
6470
5958
|
Config.DEBUG = Config.DEBUG || instance.get_config('debug');
|
|
6471
5959
|
|
|
6472
|
-
instance['__autotrack_enabled'] = instance.get_config('autotrack');
|
|
6473
|
-
if (instance.get_config('autotrack')) {
|
|
6474
|
-
var num_buckets = 100;
|
|
6475
|
-
var num_enabled_buckets = 100;
|
|
6476
|
-
if (!autotrack.enabledForProject(instance.get_config('token'), num_buckets, num_enabled_buckets)) {
|
|
6477
|
-
instance['__autotrack_enabled'] = false;
|
|
6478
|
-
console$1.log('Not in active bucket: disabling Automatic Event Collection.');
|
|
6479
|
-
} else if (!autotrack.isBrowserSupported()) {
|
|
6480
|
-
instance['__autotrack_enabled'] = false;
|
|
6481
|
-
console$1.log('Disabling Automatic Event Collection because this browser is not supported');
|
|
6482
|
-
} else {
|
|
6483
|
-
autotrack.init(instance);
|
|
6484
|
-
}
|
|
6485
|
-
}
|
|
6486
|
-
|
|
6487
5960
|
// if target is not defined, we called init after the lib already
|
|
6488
5961
|
// loaded, so there won't be an array of things to execute
|
|
6489
5962
|
if (!_.isUndefined(target) && _.isArray(target)) {
|
|
@@ -6522,11 +5995,11 @@
|
|
|
6522
5995
|
*/
|
|
6523
5996
|
MixpanelLib.prototype.init = function (token, config, name) {
|
|
6524
5997
|
if (_.isUndefined(name)) {
|
|
6525
|
-
console
|
|
5998
|
+
console.error('You must name your new library: init(token, config, name)');
|
|
6526
5999
|
return;
|
|
6527
6000
|
}
|
|
6528
6001
|
if (name === PRIMARY_INSTANCE_NAME) {
|
|
6529
|
-
console
|
|
6002
|
+
console.error('You must initialize the main mixpanel object right after you include the Mixpanel js snippet');
|
|
6530
6003
|
return;
|
|
6531
6004
|
}
|
|
6532
6005
|
|
|
@@ -6551,17 +6024,7 @@
|
|
|
6551
6024
|
this['config'] = {};
|
|
6552
6025
|
this['_triggered_notifs'] = [];
|
|
6553
6026
|
|
|
6554
|
-
|
|
6555
|
-
// (only if they have not specified a value in their init config
|
|
6556
|
-
// and they aren't using a custom API host)
|
|
6557
|
-
var variable_features = {};
|
|
6558
|
-
var api_host = config['api_host'];
|
|
6559
|
-
var is_custom_api = !!api_host && !api_host.match(/\.mixpanel\.com$/);
|
|
6560
|
-
if (!('batch_requests' in config) && !is_custom_api && determine_eligibility(token, 'batch', 60)) {
|
|
6561
|
-
variable_features['batch_requests'] = true;
|
|
6562
|
-
}
|
|
6563
|
-
|
|
6564
|
-
this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
|
|
6027
|
+
this.set_config(_.extend({}, DEFAULT_CONFIG, config, {
|
|
6565
6028
|
'name': name,
|
|
6566
6029
|
'token': token,
|
|
6567
6030
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
@@ -6583,19 +6046,36 @@
|
|
|
6583
6046
|
if (this._batch_requests) {
|
|
6584
6047
|
if (!_.localStorage.is_supported(true) || !USE_XHR) {
|
|
6585
6048
|
this._batch_requests = false;
|
|
6586
|
-
console
|
|
6049
|
+
console.log('Turning off Mixpanel request-queueing; needs XHR and localStorage support');
|
|
6587
6050
|
} else {
|
|
6588
6051
|
this.init_batchers();
|
|
6589
6052
|
if (sendBeacon && window$1.addEventListener) {
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6053
|
+
// Before page closes or hides (user tabs away etc), attempt to flush any events
|
|
6054
|
+
// queued up via navigator.sendBeacon. Since sendBeacon doesn't report success/failure,
|
|
6055
|
+
// events will not be removed from the persistent store; if the site is loaded again,
|
|
6056
|
+
// the events will be flushed again on startup and deduplicated on the Mixpanel server
|
|
6057
|
+
// side.
|
|
6058
|
+
// There is no reliable way to capture only page close events, so we lean on the
|
|
6059
|
+
// visibilitychange and pagehide events as recommended at
|
|
6060
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes.
|
|
6061
|
+
// These events fire when the user clicks away from the current page/tab, so will occur
|
|
6062
|
+
// more frequently than page unload, but are the only mechanism currently for capturing
|
|
6063
|
+
// this scenario somewhat reliably.
|
|
6064
|
+
var flush_on_unload = _.bind(function() {
|
|
6595
6065
|
if (!this.request_batchers.events.stopped) {
|
|
6596
6066
|
this.request_batchers.events.flush({unloading: true});
|
|
6597
6067
|
}
|
|
6598
|
-
}, this)
|
|
6068
|
+
}, this);
|
|
6069
|
+
window$1.addEventListener('pagehide', function(ev) {
|
|
6070
|
+
if (ev['persisted']) {
|
|
6071
|
+
flush_on_unload();
|
|
6072
|
+
}
|
|
6073
|
+
});
|
|
6074
|
+
window$1.addEventListener('visibilitychange', function() {
|
|
6075
|
+
if (document$1['visibilityState'] === 'hidden') {
|
|
6076
|
+
flush_on_unload();
|
|
6077
|
+
}
|
|
6078
|
+
});
|
|
6599
6079
|
}
|
|
6600
6080
|
}
|
|
6601
6081
|
}
|
|
@@ -6651,7 +6131,7 @@
|
|
|
6651
6131
|
|
|
6652
6132
|
MixpanelLib.prototype._track_dom = function(DomClass, args) {
|
|
6653
6133
|
if (this.get_config('img')) {
|
|
6654
|
-
console
|
|
6134
|
+
console.error('You can\'t use DOM tracking functions with img = true.');
|
|
6655
6135
|
return false;
|
|
6656
6136
|
}
|
|
6657
6137
|
|
|
@@ -6761,7 +6241,7 @@
|
|
|
6761
6241
|
try {
|
|
6762
6242
|
succeeded = sendBeacon(url, body_data);
|
|
6763
6243
|
} catch (e) {
|
|
6764
|
-
console
|
|
6244
|
+
console.error(e);
|
|
6765
6245
|
succeeded = false;
|
|
6766
6246
|
}
|
|
6767
6247
|
try {
|
|
@@ -6769,7 +6249,7 @@
|
|
|
6769
6249
|
callback(succeeded ? 1 : 0);
|
|
6770
6250
|
}
|
|
6771
6251
|
} catch (e) {
|
|
6772
|
-
console
|
|
6252
|
+
console.error(e);
|
|
6773
6253
|
}
|
|
6774
6254
|
} else if (USE_XHR) {
|
|
6775
6255
|
try {
|
|
@@ -6801,7 +6281,7 @@
|
|
|
6801
6281
|
try {
|
|
6802
6282
|
response = _.JSONDecode(req.responseText);
|
|
6803
6283
|
} catch (e) {
|
|
6804
|
-
console
|
|
6284
|
+
console.error(e);
|
|
6805
6285
|
if (options.ignore_json_errors) {
|
|
6806
6286
|
response = req.responseText;
|
|
6807
6287
|
} else {
|
|
@@ -6824,7 +6304,7 @@
|
|
|
6824
6304
|
} else {
|
|
6825
6305
|
error = 'Bad HTTP status: ' + req.status + ' ' + req.statusText;
|
|
6826
6306
|
}
|
|
6827
|
-
console
|
|
6307
|
+
console.error(error);
|
|
6828
6308
|
if (callback) {
|
|
6829
6309
|
if (verbose_mode) {
|
|
6830
6310
|
callback({status: 0, error: error, xhr_req: req});
|
|
@@ -6837,7 +6317,7 @@
|
|
|
6837
6317
|
};
|
|
6838
6318
|
req.send(body_data);
|
|
6839
6319
|
} catch (e) {
|
|
6840
|
-
console
|
|
6320
|
+
console.error(e);
|
|
6841
6321
|
succeeded = false;
|
|
6842
6322
|
}
|
|
6843
6323
|
} else {
|
|
@@ -7009,8 +6489,8 @@
|
|
|
7009
6489
|
truncated_data = this._run_hook('before_send_' + options.type, truncated_data);
|
|
7010
6490
|
}
|
|
7011
6491
|
if (truncated_data) {
|
|
7012
|
-
console
|
|
7013
|
-
console
|
|
6492
|
+
console.log('MIXPANEL REQUEST:');
|
|
6493
|
+
console.log(truncated_data);
|
|
7014
6494
|
return this._send_request(
|
|
7015
6495
|
endpoint,
|
|
7016
6496
|
encode_data_for_request(truncated_data),
|
|
@@ -7076,7 +6556,7 @@
|
|
|
7076
6556
|
}
|
|
7077
6557
|
|
|
7078
6558
|
if (_.isUndefined(event_name)) {
|
|
7079
|
-
console
|
|
6559
|
+
console.error('No event name provided to mixpanel.track');
|
|
7080
6560
|
return;
|
|
7081
6561
|
}
|
|
7082
6562
|
|
|
@@ -7117,7 +6597,7 @@
|
|
|
7117
6597
|
delete properties[blacklisted_prop];
|
|
7118
6598
|
});
|
|
7119
6599
|
} else {
|
|
7120
|
-
console
|
|
6600
|
+
console.error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
7121
6601
|
}
|
|
7122
6602
|
|
|
7123
6603
|
var data = {
|
|
@@ -7362,7 +6842,7 @@
|
|
|
7362
6842
|
*/
|
|
7363
6843
|
MixpanelLib.prototype.time_event = function(event_name) {
|
|
7364
6844
|
if (_.isUndefined(event_name)) {
|
|
7365
|
-
console
|
|
6845
|
+
console.error('No event name provided to mixpanel.time_event');
|
|
7366
6846
|
return;
|
|
7367
6847
|
}
|
|
7368
6848
|
|
|
@@ -7635,7 +7115,7 @@
|
|
|
7635
7115
|
// mixpanel.people.identify() call made for this user. It is VERY BAD to make an alias with
|
|
7636
7116
|
// this ID, as it will duplicate users.
|
|
7637
7117
|
if (alias === this.get_property(PEOPLE_DISTINCT_ID_KEY)) {
|
|
7638
|
-
console
|
|
7118
|
+
console.critical('Attempting to create alias for existing People user - aborting.');
|
|
7639
7119
|
return -2;
|
|
7640
7120
|
}
|
|
7641
7121
|
|
|
@@ -7655,7 +7135,7 @@
|
|
|
7655
7135
|
_this.identify(alias);
|
|
7656
7136
|
});
|
|
7657
7137
|
} else {
|
|
7658
|
-
console
|
|
7138
|
+
console.error('alias matches current distinct_id - skipping api call.');
|
|
7659
7139
|
this.identify(alias);
|
|
7660
7140
|
return -1;
|
|
7661
7141
|
}
|
|
@@ -7838,7 +7318,7 @@
|
|
|
7838
7318
|
MixpanelLib.prototype._run_hook = function(hook_name) {
|
|
7839
7319
|
var ret = (this['config']['hooks'][hook_name] || IDENTITY_FUNC).apply(this, slice.call(arguments, 1));
|
|
7840
7320
|
if (typeof ret === 'undefined') {
|
|
7841
|
-
console
|
|
7321
|
+
console.error(hook_name + ' hook did not return a value');
|
|
7842
7322
|
ret = null;
|
|
7843
7323
|
}
|
|
7844
7324
|
return ret;
|
|
@@ -7904,7 +7384,7 @@
|
|
|
7904
7384
|
return;
|
|
7905
7385
|
}
|
|
7906
7386
|
|
|
7907
|
-
console
|
|
7387
|
+
console.log('MIXPANEL NOTIFICATION CHECK');
|
|
7908
7388
|
|
|
7909
7389
|
var data = {
|
|
7910
7390
|
'verbose': true,
|
|
@@ -8350,18 +7830,18 @@
|
|
|
8350
7830
|
// Initialization
|
|
8351
7831
|
if (_.isUndefined(mixpanel_master)) {
|
|
8352
7832
|
// mixpanel wasn't initialized properly, report error and quit
|
|
8353
|
-
console
|
|
7833
|
+
console.critical('"mixpanel" object not initialized. Ensure you are using the latest version of the Mixpanel JS Library along with the snippet we provide.');
|
|
8354
7834
|
return;
|
|
8355
7835
|
}
|
|
8356
7836
|
if (mixpanel_master['__loaded'] || (mixpanel_master['config'] && mixpanel_master['persistence'])) {
|
|
8357
7837
|
// lib has already been loaded at least once; we don't want to override the global object this time so bomb early
|
|
8358
|
-
console
|
|
7838
|
+
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.');
|
|
8359
7839
|
return;
|
|
8360
7840
|
}
|
|
8361
7841
|
var snippet_version = mixpanel_master['__SV'] || 0;
|
|
8362
7842
|
if (snippet_version < 1.1) {
|
|
8363
7843
|
// mixpanel wasn't initialized properly, report error and quit
|
|
8364
|
-
console
|
|
7844
|
+
console.critical('Version mismatch; please ensure you\'re using the latest version of the Mixpanel code snippet.');
|
|
8365
7845
|
return;
|
|
8366
7846
|
}
|
|
8367
7847
|
|