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