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