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.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.43.0'
|
|
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) {
|
|
@@ -116,14 +116,14 @@
|
|
|
116
116
|
var log_func_with_prefix = function(func, prefix) {
|
|
117
117
|
return function() {
|
|
118
118
|
arguments[0] = '[' + prefix + '] ' + arguments[0];
|
|
119
|
-
return func.apply(console
|
|
119
|
+
return func.apply(console, arguments);
|
|
120
120
|
};
|
|
121
121
|
};
|
|
122
122
|
var console_with_prefix = function(prefix) {
|
|
123
123
|
return {
|
|
124
|
-
log: log_func_with_prefix(console
|
|
125
|
-
error: log_func_with_prefix(console
|
|
126
|
-
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)
|
|
127
127
|
};
|
|
128
128
|
};
|
|
129
129
|
|
|
@@ -285,10 +285,6 @@
|
|
|
285
285
|
return results;
|
|
286
286
|
};
|
|
287
287
|
|
|
288
|
-
_.identity = function(value) {
|
|
289
|
-
return value;
|
|
290
|
-
};
|
|
291
|
-
|
|
292
288
|
_.include = function(obj, target) {
|
|
293
289
|
var found = false;
|
|
294
290
|
if (obj === null) {
|
|
@@ -389,9 +385,9 @@
|
|
|
389
385
|
try {
|
|
390
386
|
return f.apply(this, arguments);
|
|
391
387
|
} catch (e) {
|
|
392
|
-
console
|
|
388
|
+
console.critical('Implementation error. Please turn on debug and contact support@mixpanel.com.');
|
|
393
389
|
if (Config.DEBUG){
|
|
394
|
-
console
|
|
390
|
+
console.critical(e);
|
|
395
391
|
}
|
|
396
392
|
}
|
|
397
393
|
};
|
|
@@ -952,10 +948,12 @@
|
|
|
952
948
|
// This is to block various web spiders from executing our JS and
|
|
953
949
|
// sending false tracking data
|
|
954
950
|
var BLOCKED_UA_STRS = [
|
|
951
|
+
'ahrefsbot',
|
|
955
952
|
'baiduspider',
|
|
956
953
|
'bingbot',
|
|
957
954
|
'bingpreview',
|
|
958
955
|
'facebookexternal',
|
|
956
|
+
'petalbot',
|
|
959
957
|
'pinterest',
|
|
960
958
|
'screaming frog',
|
|
961
959
|
'yahoo! slurp',
|
|
@@ -1020,16 +1018,12 @@
|
|
|
1020
1018
|
try {
|
|
1021
1019
|
result = decodeURIComponent(result);
|
|
1022
1020
|
} catch(err) {
|
|
1023
|
-
console
|
|
1021
|
+
console.error('Skipping decoding for malformed query param: ' + result);
|
|
1024
1022
|
}
|
|
1025
1023
|
return result.replace(/\+/g, ' ');
|
|
1026
1024
|
}
|
|
1027
1025
|
};
|
|
1028
1026
|
|
|
1029
|
-
_.getHashParam = function(hash, param) {
|
|
1030
|
-
var matches = hash.match(new RegExp(param + '=([^&]*)'));
|
|
1031
|
-
return matches ? matches[1] : null;
|
|
1032
|
-
};
|
|
1033
1027
|
|
|
1034
1028
|
// _.cookie
|
|
1035
1029
|
// Methods partially borrowed from quirksmode.org/js/cookies.html
|
|
@@ -1151,13 +1145,13 @@
|
|
|
1151
1145
|
is_supported: function(force_check) {
|
|
1152
1146
|
var supported = localStorageSupported(null, force_check);
|
|
1153
1147
|
if (!supported) {
|
|
1154
|
-
console
|
|
1148
|
+
console.error('localStorage unsupported; falling back to cookie store');
|
|
1155
1149
|
}
|
|
1156
1150
|
return supported;
|
|
1157
1151
|
},
|
|
1158
1152
|
|
|
1159
1153
|
error: function(msg) {
|
|
1160
|
-
console
|
|
1154
|
+
console.error('localStorage error: ' + msg);
|
|
1161
1155
|
},
|
|
1162
1156
|
|
|
1163
1157
|
get: function(name) {
|
|
@@ -1212,7 +1206,7 @@
|
|
|
1212
1206
|
*/
|
|
1213
1207
|
var register_event = function(element, type, handler, oldSchool, useCapture) {
|
|
1214
1208
|
if (!element) {
|
|
1215
|
-
console
|
|
1209
|
+
console.error('No valid element provided to register_event');
|
|
1216
1210
|
return;
|
|
1217
1211
|
}
|
|
1218
1212
|
|
|
@@ -1696,28 +1690,6 @@
|
|
|
1696
1690
|
return maxlen ? guid.substring(0, maxlen) : guid;
|
|
1697
1691
|
};
|
|
1698
1692
|
|
|
1699
|
-
/**
|
|
1700
|
-
* Check deterministically whether to include or exclude from a feature rollout/test based on the
|
|
1701
|
-
* given string and the desired percentage to include.
|
|
1702
|
-
* @param {String} str - string to run the check against (for instance a project's token)
|
|
1703
|
-
* @param {String} feature - name of feature (for inclusion in hash, to ensure different results
|
|
1704
|
-
* for different features)
|
|
1705
|
-
* @param {Number} percent_allowed - percentage chance that a given string will be included
|
|
1706
|
-
* @returns {Boolean} whether the given string should be included
|
|
1707
|
-
*/
|
|
1708
|
-
var determine_eligibility = _.safewrap(function(str, feature, percent_allowed) {
|
|
1709
|
-
str = str + feature;
|
|
1710
|
-
|
|
1711
|
-
// Bernstein's hash: http://www.cse.yorku.ca/~oz/hash.html#djb2
|
|
1712
|
-
var hash = 5381;
|
|
1713
|
-
for (var i = 0; i < str.length; i++) {
|
|
1714
|
-
hash = ((hash << 5) + hash) + str.charCodeAt(i);
|
|
1715
|
-
hash = hash & hash;
|
|
1716
|
-
}
|
|
1717
|
-
var dart = (hash >>> 0) % 100;
|
|
1718
|
-
return dart < percent_allowed;
|
|
1719
|
-
});
|
|
1720
|
-
|
|
1721
1693
|
// naive way to extract domain name (example.com) from full hostname (my.sub.example.com)
|
|
1722
1694
|
var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i;
|
|
1723
1695
|
// this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk
|
|
@@ -1768,539 +1740,6 @@
|
|
|
1768
1740
|
_['info']['browserVersion'] = _.info.browserVersion;
|
|
1769
1741
|
_['info']['properties'] = _.info.properties;
|
|
1770
1742
|
|
|
1771
|
-
/*
|
|
1772
|
-
* Get the className of an element, accounting for edge cases where element.className is an object
|
|
1773
|
-
* @param {Element} el - element to get the className of
|
|
1774
|
-
* @returns {string} the element's class
|
|
1775
|
-
*/
|
|
1776
|
-
function getClassName(el) {
|
|
1777
|
-
switch(typeof el.className) {
|
|
1778
|
-
case 'string':
|
|
1779
|
-
return el.className;
|
|
1780
|
-
case 'object': // handle cases where className might be SVGAnimatedString or some other type
|
|
1781
|
-
return el.className.baseVal || el.getAttribute('class') || '';
|
|
1782
|
-
default: // future proof
|
|
1783
|
-
return '';
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
/*
|
|
1788
|
-
* Get the direct text content of an element, protecting against sensitive data collection.
|
|
1789
|
-
* Concats textContent of each of the element's text node children; this avoids potential
|
|
1790
|
-
* collection of sensitive data that could happen if we used element.textContent and the
|
|
1791
|
-
* element had sensitive child elements, since element.textContent includes child content.
|
|
1792
|
-
* Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
|
|
1793
|
-
* @param {Element} el - element to get the text of
|
|
1794
|
-
* @returns {string} the element's direct text content
|
|
1795
|
-
*/
|
|
1796
|
-
function getSafeText(el) {
|
|
1797
|
-
var elText = '';
|
|
1798
|
-
|
|
1799
|
-
if (shouldTrackElement(el) && el.childNodes && el.childNodes.length) {
|
|
1800
|
-
_.each(el.childNodes, function(child) {
|
|
1801
|
-
if (isTextNode(child) && child.textContent) {
|
|
1802
|
-
elText += _.trim(child.textContent)
|
|
1803
|
-
// scrub potentially sensitive values
|
|
1804
|
-
.split(/(\s+)/).filter(shouldTrackValue).join('')
|
|
1805
|
-
// normalize whitespace
|
|
1806
|
-
.replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ')
|
|
1807
|
-
// truncate
|
|
1808
|
-
.substring(0, 255);
|
|
1809
|
-
}
|
|
1810
|
-
});
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
return _.trim(elText);
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
/*
|
|
1817
|
-
* Check whether an element has nodeType Node.ELEMENT_NODE
|
|
1818
|
-
* @param {Element} el - element to check
|
|
1819
|
-
* @returns {boolean} whether el is of the correct nodeType
|
|
1820
|
-
*/
|
|
1821
|
-
function isElementNode(el) {
|
|
1822
|
-
return el && el.nodeType === 1; // Node.ELEMENT_NODE - use integer constant for browser portability
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
/*
|
|
1826
|
-
* Check whether an element is of a given tag type.
|
|
1827
|
-
* Due to potential reference discrepancies (such as the webcomponents.js polyfill),
|
|
1828
|
-
* we want to match tagNames instead of specific references because something like
|
|
1829
|
-
* element === document.body won't always work because element might not be a native
|
|
1830
|
-
* element.
|
|
1831
|
-
* @param {Element} el - element to check
|
|
1832
|
-
* @param {string} tag - tag name (e.g., "div")
|
|
1833
|
-
* @returns {boolean} whether el is of the given tag type
|
|
1834
|
-
*/
|
|
1835
|
-
function isTag(el, tag) {
|
|
1836
|
-
return el && el.tagName && el.tagName.toLowerCase() === tag.toLowerCase();
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
/*
|
|
1840
|
-
* Check whether an element has nodeType Node.TEXT_NODE
|
|
1841
|
-
* @param {Element} el - element to check
|
|
1842
|
-
* @returns {boolean} whether el is of the correct nodeType
|
|
1843
|
-
*/
|
|
1844
|
-
function isTextNode(el) {
|
|
1845
|
-
return el && el.nodeType === 3; // Node.TEXT_NODE - use integer constant for browser portability
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
/*
|
|
1849
|
-
* Check whether a DOM event should be "tracked" or if it may contain sentitive data
|
|
1850
|
-
* using a variety of heuristics.
|
|
1851
|
-
* @param {Element} el - element to check
|
|
1852
|
-
* @param {Event} event - event to check
|
|
1853
|
-
* @returns {boolean} whether the event should be tracked
|
|
1854
|
-
*/
|
|
1855
|
-
function shouldTrackDomEvent(el, event) {
|
|
1856
|
-
if (!el || isTag(el, 'html') || !isElementNode(el)) {
|
|
1857
|
-
return false;
|
|
1858
|
-
}
|
|
1859
|
-
var tag = el.tagName.toLowerCase();
|
|
1860
|
-
switch (tag) {
|
|
1861
|
-
case 'html':
|
|
1862
|
-
return false;
|
|
1863
|
-
case 'form':
|
|
1864
|
-
return event.type === 'submit';
|
|
1865
|
-
case 'input':
|
|
1866
|
-
if (['button', 'submit'].indexOf(el.getAttribute('type')) === -1) {
|
|
1867
|
-
return event.type === 'change';
|
|
1868
|
-
} else {
|
|
1869
|
-
return event.type === 'click';
|
|
1870
|
-
}
|
|
1871
|
-
case 'select':
|
|
1872
|
-
case 'textarea':
|
|
1873
|
-
return event.type === 'change';
|
|
1874
|
-
default:
|
|
1875
|
-
return event.type === 'click';
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
/*
|
|
1880
|
-
* Check whether a DOM element should be "tracked" or if it may contain sentitive data
|
|
1881
|
-
* using a variety of heuristics.
|
|
1882
|
-
* @param {Element} el - element to check
|
|
1883
|
-
* @returns {boolean} whether the element should be tracked
|
|
1884
|
-
*/
|
|
1885
|
-
function shouldTrackElement(el) {
|
|
1886
|
-
for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
|
|
1887
|
-
var classes = getClassName(curEl).split(' ');
|
|
1888
|
-
if (_.includes(classes, 'mp-sensitive') || _.includes(classes, 'mp-no-track')) {
|
|
1889
|
-
return false;
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1893
|
-
if (_.includes(getClassName(el).split(' '), 'mp-include')) {
|
|
1894
|
-
return true;
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
// don't send data from inputs or similar elements since there will always be
|
|
1898
|
-
// a risk of clientside javascript placing sensitive data in attributes
|
|
1899
|
-
if (
|
|
1900
|
-
isTag(el, 'input') ||
|
|
1901
|
-
isTag(el, 'select') ||
|
|
1902
|
-
isTag(el, 'textarea') ||
|
|
1903
|
-
el.getAttribute('contenteditable') === 'true'
|
|
1904
|
-
) {
|
|
1905
|
-
return false;
|
|
1906
|
-
}
|
|
1907
|
-
|
|
1908
|
-
// don't include hidden or password fields
|
|
1909
|
-
var type = el.type || '';
|
|
1910
|
-
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"]
|
|
1911
|
-
switch(type.toLowerCase()) {
|
|
1912
|
-
case 'hidden':
|
|
1913
|
-
return false;
|
|
1914
|
-
case 'password':
|
|
1915
|
-
return false;
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
// filter out data from fields that look like sensitive fields
|
|
1920
|
-
var name = el.name || el.id || '';
|
|
1921
|
-
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"]
|
|
1922
|
-
var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
|
|
1923
|
-
if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
|
|
1924
|
-
return false;
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
return true;
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
|
-
/*
|
|
1932
|
-
* Check whether a string value should be "tracked" or if it may contain sentitive data
|
|
1933
|
-
* using a variety of heuristics.
|
|
1934
|
-
* @param {string} value - string value to check
|
|
1935
|
-
* @returns {boolean} whether the element should be tracked
|
|
1936
|
-
*/
|
|
1937
|
-
function shouldTrackValue(value) {
|
|
1938
|
-
if (value === null || _.isUndefined(value)) {
|
|
1939
|
-
return false;
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
if (typeof value === 'string') {
|
|
1943
|
-
value = _.trim(value);
|
|
1944
|
-
|
|
1945
|
-
// check to see if input value looks like a credit card number
|
|
1946
|
-
// see: https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s20.html
|
|
1947
|
-
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}))$/;
|
|
1948
|
-
if (ccRegex.test((value || '').replace(/[- ]/g, ''))) {
|
|
1949
|
-
return false;
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
// check to see if input value looks like a social security number
|
|
1953
|
-
var ssnRegex = /(^\d{3}-?\d{2}-?\d{4}$)/;
|
|
1954
|
-
if (ssnRegex.test(value)) {
|
|
1955
|
-
return false;
|
|
1956
|
-
}
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
return true;
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
var autotrack = {
|
|
1963
|
-
_initializedTokens: [],
|
|
1964
|
-
|
|
1965
|
-
_previousElementSibling: function(el) {
|
|
1966
|
-
if (el.previousElementSibling) {
|
|
1967
|
-
return el.previousElementSibling;
|
|
1968
|
-
} else {
|
|
1969
|
-
do {
|
|
1970
|
-
el = el.previousSibling;
|
|
1971
|
-
} while (el && !isElementNode(el));
|
|
1972
|
-
return el;
|
|
1973
|
-
}
|
|
1974
|
-
},
|
|
1975
|
-
|
|
1976
|
-
_loadScript: function(scriptUrlToLoad, callback) {
|
|
1977
|
-
var scriptTag = document.createElement('script');
|
|
1978
|
-
scriptTag.type = 'text/javascript';
|
|
1979
|
-
scriptTag.src = scriptUrlToLoad;
|
|
1980
|
-
scriptTag.onload = callback;
|
|
1981
|
-
|
|
1982
|
-
var scripts = document.getElementsByTagName('script');
|
|
1983
|
-
if (scripts.length > 0) {
|
|
1984
|
-
scripts[0].parentNode.insertBefore(scriptTag, scripts[0]);
|
|
1985
|
-
} else {
|
|
1986
|
-
document.body.appendChild(scriptTag);
|
|
1987
|
-
}
|
|
1988
|
-
},
|
|
1989
|
-
|
|
1990
|
-
_getPropertiesFromElement: function(elem) {
|
|
1991
|
-
var props = {
|
|
1992
|
-
'classes': getClassName(elem).split(' '),
|
|
1993
|
-
'tag_name': elem.tagName.toLowerCase()
|
|
1994
|
-
};
|
|
1995
|
-
|
|
1996
|
-
if (shouldTrackElement(elem)) {
|
|
1997
|
-
_.each(elem.attributes, function(attr) {
|
|
1998
|
-
if (shouldTrackValue(attr.value)) {
|
|
1999
|
-
props['attr__' + attr.name] = attr.value;
|
|
2000
|
-
}
|
|
2001
|
-
});
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
var nthChild = 1;
|
|
2005
|
-
var nthOfType = 1;
|
|
2006
|
-
var currentElem = elem;
|
|
2007
|
-
while (currentElem = this._previousElementSibling(currentElem)) { // eslint-disable-line no-cond-assign
|
|
2008
|
-
nthChild++;
|
|
2009
|
-
if (currentElem.tagName === elem.tagName) {
|
|
2010
|
-
nthOfType++;
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
props['nth_child'] = nthChild;
|
|
2014
|
-
props['nth_of_type'] = nthOfType;
|
|
2015
|
-
|
|
2016
|
-
return props;
|
|
2017
|
-
},
|
|
2018
|
-
|
|
2019
|
-
_getDefaultProperties: function(eventType) {
|
|
2020
|
-
return {
|
|
2021
|
-
'$event_type': eventType,
|
|
2022
|
-
'$ce_version': 1,
|
|
2023
|
-
'$host': window.location.host,
|
|
2024
|
-
'$pathname': window.location.pathname
|
|
2025
|
-
};
|
|
2026
|
-
},
|
|
2027
|
-
|
|
2028
|
-
_extractCustomPropertyValue: function(customProperty) {
|
|
2029
|
-
var propValues = [];
|
|
2030
|
-
_.each(document.querySelectorAll(customProperty['css_selector']), function(matchedElem) {
|
|
2031
|
-
var value;
|
|
2032
|
-
|
|
2033
|
-
if (['input', 'select'].indexOf(matchedElem.tagName.toLowerCase()) > -1) {
|
|
2034
|
-
value = matchedElem['value'];
|
|
2035
|
-
} else if (matchedElem['textContent']) {
|
|
2036
|
-
value = matchedElem['textContent'];
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
|
-
if (shouldTrackValue(value)) {
|
|
2040
|
-
propValues.push(value);
|
|
2041
|
-
}
|
|
2042
|
-
});
|
|
2043
|
-
return propValues.join(', ');
|
|
2044
|
-
},
|
|
2045
|
-
|
|
2046
|
-
_getCustomProperties: function(targetElementList) {
|
|
2047
|
-
var props = {};
|
|
2048
|
-
_.each(this._customProperties, function(customProperty) {
|
|
2049
|
-
_.each(customProperty['event_selectors'], function(eventSelector) {
|
|
2050
|
-
var eventElements = document.querySelectorAll(eventSelector);
|
|
2051
|
-
_.each(eventElements, function(eventElement) {
|
|
2052
|
-
if (_.includes(targetElementList, eventElement) && shouldTrackElement(eventElement)) {
|
|
2053
|
-
props[customProperty['name']] = this._extractCustomPropertyValue(customProperty);
|
|
2054
|
-
}
|
|
2055
|
-
}, this);
|
|
2056
|
-
}, this);
|
|
2057
|
-
}, this);
|
|
2058
|
-
return props;
|
|
2059
|
-
},
|
|
2060
|
-
|
|
2061
|
-
_getEventTarget: function(e) {
|
|
2062
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/Event/target#Compatibility_notes
|
|
2063
|
-
if (typeof e.target === 'undefined') {
|
|
2064
|
-
return e.srcElement;
|
|
2065
|
-
} else {
|
|
2066
|
-
return e.target;
|
|
2067
|
-
}
|
|
2068
|
-
},
|
|
2069
|
-
|
|
2070
|
-
_trackEvent: function(e, instance) {
|
|
2071
|
-
/*** Don't mess with this code without running IE8 tests on it ***/
|
|
2072
|
-
var target = this._getEventTarget(e);
|
|
2073
|
-
if (isTextNode(target)) { // defeat Safari bug (see: http://www.quirksmode.org/js/events_properties.html)
|
|
2074
|
-
target = target.parentNode;
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
if (shouldTrackDomEvent(target, e)) {
|
|
2078
|
-
var targetElementList = [target];
|
|
2079
|
-
var curEl = target;
|
|
2080
|
-
while (curEl.parentNode && !isTag(curEl, 'body')) {
|
|
2081
|
-
targetElementList.push(curEl.parentNode);
|
|
2082
|
-
curEl = curEl.parentNode;
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
var elementsJson = [];
|
|
2086
|
-
var href, explicitNoTrack = false;
|
|
2087
|
-
_.each(targetElementList, function(el) {
|
|
2088
|
-
var shouldTrackEl = shouldTrackElement(el);
|
|
2089
|
-
|
|
2090
|
-
// if the element or a parent element is an anchor tag
|
|
2091
|
-
// include the href as a property
|
|
2092
|
-
if (el.tagName.toLowerCase() === 'a') {
|
|
2093
|
-
href = el.getAttribute('href');
|
|
2094
|
-
href = shouldTrackEl && shouldTrackValue(href) && href;
|
|
2095
|
-
}
|
|
2096
|
-
|
|
2097
|
-
// allow users to programatically prevent tracking of elements by adding class 'mp-no-track'
|
|
2098
|
-
var classes = getClassName(el).split(' ');
|
|
2099
|
-
if (_.includes(classes, 'mp-no-track')) {
|
|
2100
|
-
explicitNoTrack = true;
|
|
2101
|
-
}
|
|
2102
|
-
|
|
2103
|
-
elementsJson.push(this._getPropertiesFromElement(el));
|
|
2104
|
-
}, this);
|
|
2105
|
-
|
|
2106
|
-
if (explicitNoTrack) {
|
|
2107
|
-
return false;
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
// only populate text content from target element (not parents)
|
|
2111
|
-
// to prevent text within a sensitive element from being collected
|
|
2112
|
-
// as part of a parent's el.textContent
|
|
2113
|
-
var elementText;
|
|
2114
|
-
var safeElementText = getSafeText(target);
|
|
2115
|
-
if (safeElementText && safeElementText.length) {
|
|
2116
|
-
elementText = safeElementText;
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
var props = _.extend(
|
|
2120
|
-
this._getDefaultProperties(e.type),
|
|
2121
|
-
{
|
|
2122
|
-
'$elements': elementsJson,
|
|
2123
|
-
'$el_attr__href': href,
|
|
2124
|
-
'$el_text': elementText
|
|
2125
|
-
},
|
|
2126
|
-
this._getCustomProperties(targetElementList)
|
|
2127
|
-
);
|
|
2128
|
-
|
|
2129
|
-
instance.track('$web_event', props);
|
|
2130
|
-
return true;
|
|
2131
|
-
}
|
|
2132
|
-
},
|
|
2133
|
-
|
|
2134
|
-
// only reason is to stub for unit tests
|
|
2135
|
-
// since you can't override window.location props
|
|
2136
|
-
_navigate: function(href) {
|
|
2137
|
-
window.location.href = href;
|
|
2138
|
-
},
|
|
2139
|
-
|
|
2140
|
-
_addDomEventHandlers: function(instance) {
|
|
2141
|
-
var handler = _.bind(function(e) {
|
|
2142
|
-
e = e || window.event;
|
|
2143
|
-
this._trackEvent(e, instance);
|
|
2144
|
-
}, this);
|
|
2145
|
-
_.register_event(document, 'submit', handler, false, true);
|
|
2146
|
-
_.register_event(document, 'change', handler, false, true);
|
|
2147
|
-
_.register_event(document, 'click', handler, false, true);
|
|
2148
|
-
},
|
|
2149
|
-
|
|
2150
|
-
_customProperties: {},
|
|
2151
|
-
init: function(instance) {
|
|
2152
|
-
if (!(document && document.body)) {
|
|
2153
|
-
console.log('document not ready yet, trying again in 500 milliseconds...');
|
|
2154
|
-
var that = this;
|
|
2155
|
-
setTimeout(function() { that.init(instance); }, 500);
|
|
2156
|
-
return;
|
|
2157
|
-
}
|
|
2158
|
-
|
|
2159
|
-
var token = instance.get_config('token');
|
|
2160
|
-
if (this._initializedTokens.indexOf(token) > -1) {
|
|
2161
|
-
console.log('autotrack already initialized for token "' + token + '"');
|
|
2162
|
-
return;
|
|
2163
|
-
}
|
|
2164
|
-
this._initializedTokens.push(token);
|
|
2165
|
-
|
|
2166
|
-
if (!this._maybeLoadEditor(instance)) { // don't autotrack actions when the editor is enabled
|
|
2167
|
-
var parseDecideResponse = _.bind(function(response) {
|
|
2168
|
-
if (response && response['config'] && response['config']['enable_collect_everything'] === true) {
|
|
2169
|
-
|
|
2170
|
-
if (response['custom_properties']) {
|
|
2171
|
-
this._customProperties = response['custom_properties'];
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
|
-
instance.track('$web_event', _.extend({
|
|
2175
|
-
'$title': document.title
|
|
2176
|
-
}, this._getDefaultProperties('pageview')));
|
|
2177
|
-
|
|
2178
|
-
this._addDomEventHandlers(instance);
|
|
2179
|
-
|
|
2180
|
-
} else {
|
|
2181
|
-
instance['__autotrack_enabled'] = false;
|
|
2182
|
-
}
|
|
2183
|
-
}, this);
|
|
2184
|
-
|
|
2185
|
-
instance._send_request(
|
|
2186
|
-
instance.get_config('api_host') + '/decide/', {
|
|
2187
|
-
'verbose': true,
|
|
2188
|
-
'version': '1',
|
|
2189
|
-
'lib': 'web',
|
|
2190
|
-
'token': token
|
|
2191
|
-
},
|
|
2192
|
-
{method: 'GET', transport: 'XHR'},
|
|
2193
|
-
instance._prepare_callback(parseDecideResponse)
|
|
2194
|
-
);
|
|
2195
|
-
}
|
|
2196
|
-
},
|
|
2197
|
-
|
|
2198
|
-
_editorParamsFromHash: function(instance, hash) {
|
|
2199
|
-
var editorParams;
|
|
2200
|
-
try {
|
|
2201
|
-
var state = _.getHashParam(hash, 'state');
|
|
2202
|
-
state = JSON.parse(decodeURIComponent(state));
|
|
2203
|
-
var expiresInSeconds = _.getHashParam(hash, 'expires_in');
|
|
2204
|
-
editorParams = {
|
|
2205
|
-
'accessToken': _.getHashParam(hash, 'access_token'),
|
|
2206
|
-
'accessTokenExpiresAt': (new Date()).getTime() + (Number(expiresInSeconds) * 1000),
|
|
2207
|
-
'bookmarkletMode': !!state['bookmarkletMode'],
|
|
2208
|
-
'projectId': state['projectId'],
|
|
2209
|
-
'projectOwnerId': state['projectOwnerId'],
|
|
2210
|
-
'projectToken': state['token'],
|
|
2211
|
-
'readOnly': state['readOnly'],
|
|
2212
|
-
'userFlags': state['userFlags'],
|
|
2213
|
-
'userId': state['userId']
|
|
2214
|
-
};
|
|
2215
|
-
window.sessionStorage.setItem('editorParams', JSON.stringify(editorParams));
|
|
2216
|
-
|
|
2217
|
-
if (state['desiredHash']) {
|
|
2218
|
-
window.location.hash = state['desiredHash'];
|
|
2219
|
-
} else if (window.history) {
|
|
2220
|
-
history.replaceState('', document.title, window.location.pathname + window.location.search); // completely remove hash
|
|
2221
|
-
} else {
|
|
2222
|
-
window.location.hash = ''; // clear hash (but leaves # unfortunately)
|
|
2223
|
-
}
|
|
2224
|
-
} catch (e) {
|
|
2225
|
-
console.error('Unable to parse data from hash', e);
|
|
2226
|
-
}
|
|
2227
|
-
return editorParams;
|
|
2228
|
-
},
|
|
2229
|
-
|
|
2230
|
-
/**
|
|
2231
|
-
* To load the visual editor, we need an access token and other state. That state comes from one of three places:
|
|
2232
|
-
* 1. In the URL hash params if the customer is using an old snippet
|
|
2233
|
-
* 2. From session storage under the key `_mpcehash` if the snippet already parsed the hash
|
|
2234
|
-
* 3. From session storage under the key `editorParams` if the editor was initialized on a previous page
|
|
2235
|
-
*/
|
|
2236
|
-
_maybeLoadEditor: function(instance) {
|
|
2237
|
-
try {
|
|
2238
|
-
var parseFromUrl = false;
|
|
2239
|
-
if (_.getHashParam(window.location.hash, 'state')) {
|
|
2240
|
-
var state = _.getHashParam(window.location.hash, 'state');
|
|
2241
|
-
state = JSON.parse(decodeURIComponent(state));
|
|
2242
|
-
parseFromUrl = state['action'] === 'mpeditor';
|
|
2243
|
-
}
|
|
2244
|
-
var parseFromStorage = !!window.sessionStorage.getItem('_mpcehash');
|
|
2245
|
-
var editorParams;
|
|
2246
|
-
|
|
2247
|
-
if (parseFromUrl) { // happens if they are initializing the editor using an old snippet
|
|
2248
|
-
editorParams = this._editorParamsFromHash(instance, window.location.hash);
|
|
2249
|
-
} else if (parseFromStorage) { // happens if they are initialized the editor and using the new snippet
|
|
2250
|
-
editorParams = this._editorParamsFromHash(instance, window.sessionStorage.getItem('_mpcehash'));
|
|
2251
|
-
window.sessionStorage.removeItem('_mpcehash');
|
|
2252
|
-
} else { // get credentials from sessionStorage from a previous initialzation
|
|
2253
|
-
editorParams = JSON.parse(window.sessionStorage.getItem('editorParams') || '{}');
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2256
|
-
if (editorParams['projectToken'] && instance.get_config('token') === editorParams['projectToken']) {
|
|
2257
|
-
this._loadEditor(instance, editorParams);
|
|
2258
|
-
return true;
|
|
2259
|
-
} else {
|
|
2260
|
-
return false;
|
|
2261
|
-
}
|
|
2262
|
-
} catch (e) {
|
|
2263
|
-
return false;
|
|
2264
|
-
}
|
|
2265
|
-
},
|
|
2266
|
-
|
|
2267
|
-
_loadEditor: function(instance, editorParams) {
|
|
2268
|
-
if (!window['_mpEditorLoaded']) { // only load the codeless event editor once, even if there are multiple instances of MixpanelLib
|
|
2269
|
-
window['_mpEditorLoaded'] = true;
|
|
2270
|
-
var editorUrl = instance.get_config('app_host')
|
|
2271
|
-
+ '/js-bundle/reports/collect-everything/editor.js?_ts='
|
|
2272
|
-
+ (new Date()).getTime();
|
|
2273
|
-
this._loadScript(editorUrl, function() {
|
|
2274
|
-
window['mp_load_editor'](editorParams);
|
|
2275
|
-
});
|
|
2276
|
-
return true;
|
|
2277
|
-
}
|
|
2278
|
-
return false;
|
|
2279
|
-
},
|
|
2280
|
-
|
|
2281
|
-
// this is a mechanism to ramp up CE with no server-side interaction.
|
|
2282
|
-
// when CE is active, every page load results in a decide request. we
|
|
2283
|
-
// need to gently ramp this up so we don't overload decide. this decides
|
|
2284
|
-
// deterministically if CE is enabled for this project by modding the char
|
|
2285
|
-
// value of the project token.
|
|
2286
|
-
enabledForProject: function(token, numBuckets, numEnabledBuckets) {
|
|
2287
|
-
numBuckets = !_.isUndefined(numBuckets) ? numBuckets : 10;
|
|
2288
|
-
numEnabledBuckets = !_.isUndefined(numEnabledBuckets) ? numEnabledBuckets : 10;
|
|
2289
|
-
var charCodeSum = 0;
|
|
2290
|
-
for (var i = 0; i < token.length; i++) {
|
|
2291
|
-
charCodeSum += token.charCodeAt(i);
|
|
2292
|
-
}
|
|
2293
|
-
return (charCodeSum % numBuckets) < numEnabledBuckets;
|
|
2294
|
-
},
|
|
2295
|
-
|
|
2296
|
-
isBrowserSupported: function() {
|
|
2297
|
-
return _.isFunction(document.querySelectorAll);
|
|
2298
|
-
}
|
|
2299
|
-
};
|
|
2300
|
-
|
|
2301
|
-
_.bind_instance_methods(autotrack);
|
|
2302
|
-
_.safewrap_instance_methods(autotrack);
|
|
2303
|
-
|
|
2304
1743
|
/**
|
|
2305
1744
|
* DomTracker Object
|
|
2306
1745
|
* @constructor
|
|
@@ -2329,7 +1768,7 @@
|
|
|
2329
1768
|
var elements = _.dom_query(query);
|
|
2330
1769
|
|
|
2331
1770
|
if (elements.length === 0) {
|
|
2332
|
-
console
|
|
1771
|
+
console.error('The DOM query (' + query + ') returned 0 elements');
|
|
2333
1772
|
return;
|
|
2334
1773
|
}
|
|
2335
1774
|
|
|
@@ -2989,9 +2428,9 @@
|
|
|
2989
2428
|
} else if (
|
|
2990
2429
|
_.isObject(res) &&
|
|
2991
2430
|
res.xhr_req &&
|
|
2992
|
-
(res.xhr_req['status'] >= 500 || res.xhr_req['status']
|
|
2431
|
+
(res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout')
|
|
2993
2432
|
) {
|
|
2994
|
-
// network or API error, retry
|
|
2433
|
+
// network or API error, or 429 Too Many Requests, retry
|
|
2995
2434
|
var retryMS = this.flushInterval * 2;
|
|
2996
2435
|
var headers = res.xhr_req['responseHeaders'];
|
|
2997
2436
|
if (headers) {
|
|
@@ -3121,12 +2560,12 @@
|
|
|
3121
2560
|
*/
|
|
3122
2561
|
function hasOptedOut(token, options) {
|
|
3123
2562
|
if (_hasDoNotTrackFlagOn(options)) {
|
|
3124
|
-
console
|
|
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"');
|
|
3125
2564
|
return true;
|
|
3126
2565
|
}
|
|
3127
2566
|
var optedOut = _getStorageValue(token, options) === '0';
|
|
3128
2567
|
if (optedOut) {
|
|
3129
|
-
console
|
|
2568
|
+
console.warn('You are opted out of Mixpanel tracking. This will prevent the Mixpanel SDK from sending any data.');
|
|
3130
2569
|
}
|
|
3131
2570
|
return optedOut;
|
|
3132
2571
|
}
|
|
@@ -3270,7 +2709,7 @@
|
|
|
3270
2709
|
*/
|
|
3271
2710
|
function _optInOut(optValue, token, options) {
|
|
3272
2711
|
if (!_.isString(token) || !token.length) {
|
|
3273
|
-
console
|
|
2712
|
+
console.error('gdpr.' + (optValue ? 'optIn' : 'optOut') + ' called with an invalid token');
|
|
3274
2713
|
return;
|
|
3275
2714
|
}
|
|
3276
2715
|
|
|
@@ -3321,7 +2760,7 @@
|
|
|
3321
2760
|
});
|
|
3322
2761
|
}
|
|
3323
2762
|
} catch(err) {
|
|
3324
|
-
console
|
|
2763
|
+
console.error('Unexpected error when checking tracking opt-out status: ' + err);
|
|
3325
2764
|
}
|
|
3326
2765
|
|
|
3327
2766
|
if (!optedOut) {
|
|
@@ -3561,9 +3000,13 @@
|
|
|
3561
3000
|
* Permanently delete a group.
|
|
3562
3001
|
*
|
|
3563
3002
|
* ### Usage:
|
|
3003
|
+
*
|
|
3564
3004
|
* mixpanel.get_group('company', 'mixpanel').delete();
|
|
3005
|
+
*
|
|
3006
|
+
* @param {Function} [callback] If provided, the callback will be called after the tracking event
|
|
3565
3007
|
*/
|
|
3566
3008
|
MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) {
|
|
3009
|
+
// bracket notation above prevents a minification error related to reserved words
|
|
3567
3010
|
var data = this.delete_action();
|
|
3568
3011
|
return this._send_request(data, callback);
|
|
3569
3012
|
});
|
|
@@ -3663,7 +3106,7 @@
|
|
|
3663
3106
|
|
|
3664
3107
|
var storage_type = config['persistence'];
|
|
3665
3108
|
if (storage_type !== 'cookie' && storage_type !== 'localStorage') {
|
|
3666
|
-
console
|
|
3109
|
+
console.critical('Unknown persistence type ' + storage_type + '; falling back to cookie');
|
|
3667
3110
|
storage_type = config['persistence'] = 'cookie';
|
|
3668
3111
|
}
|
|
3669
3112
|
|
|
@@ -4021,8 +3464,8 @@
|
|
|
4021
3464
|
this._pop_from_people_queue(UNSET_ACTION, q_data);
|
|
4022
3465
|
}
|
|
4023
3466
|
|
|
4024
|
-
console
|
|
4025
|
-
console
|
|
3467
|
+
console.log('MIXPANEL PEOPLE REQUEST (QUEUED, PENDING IDENTIFY):');
|
|
3468
|
+
console.log(data);
|
|
4026
3469
|
|
|
4027
3470
|
this.save();
|
|
4028
3471
|
};
|
|
@@ -4065,7 +3508,7 @@
|
|
|
4065
3508
|
} else if (queue === UNION_ACTION) {
|
|
4066
3509
|
return UNION_QUEUE_KEY;
|
|
4067
3510
|
} else {
|
|
4068
|
-
console
|
|
3511
|
+
console.error('Invalid queue:', queue);
|
|
4069
3512
|
}
|
|
4070
3513
|
};
|
|
4071
3514
|
|
|
@@ -6033,7 +5476,7 @@
|
|
|
6033
5476
|
_.each(prop, function(v, k) {
|
|
6034
5477
|
if (!this._is_reserved_property(k)) {
|
|
6035
5478
|
if (isNaN(parseFloat(v))) {
|
|
6036
|
-
console
|
|
5479
|
+
console.error('Invalid increment value passed to mixpanel.people.increment - must be a number');
|
|
6037
5480
|
return;
|
|
6038
5481
|
} else {
|
|
6039
5482
|
$add[k] = v;
|
|
@@ -6157,7 +5600,7 @@
|
|
|
6157
5600
|
if (!_.isNumber(amount)) {
|
|
6158
5601
|
amount = parseFloat(amount);
|
|
6159
5602
|
if (isNaN(amount)) {
|
|
6160
|
-
console
|
|
5603
|
+
console.error('Invalid value passed to mixpanel.people.track_charge - must be a number');
|
|
6161
5604
|
return;
|
|
6162
5605
|
}
|
|
6163
5606
|
}
|
|
@@ -6193,7 +5636,7 @@
|
|
|
6193
5636
|
*/
|
|
6194
5637
|
MixpanelPeople.prototype.delete_user = function() {
|
|
6195
5638
|
if (!this._identify_called()) {
|
|
6196
|
-
console
|
|
5639
|
+
console.error('mixpanel.people.delete_user() requires you to call identify() first');
|
|
6197
5640
|
return;
|
|
6198
5641
|
}
|
|
6199
5642
|
var data = {'$delete': this._mixpanel.get_distinct_id()};
|
|
@@ -6267,7 +5710,7 @@
|
|
|
6267
5710
|
} else if (UNION_ACTION in data) {
|
|
6268
5711
|
this._mixpanel['persistence']._add_to_people_queue(UNION_ACTION, data);
|
|
6269
5712
|
} else {
|
|
6270
|
-
console
|
|
5713
|
+
console.error('Invalid call to _enqueue():', data);
|
|
6271
5714
|
}
|
|
6272
5715
|
};
|
|
6273
5716
|
|
|
@@ -6404,6 +5847,8 @@
|
|
|
6404
5847
|
var NOOP_FUNC = function() {};
|
|
6405
5848
|
|
|
6406
5849
|
/** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
|
|
5850
|
+
/** @const */ var PAYLOAD_TYPE_BASE64 = 'base64';
|
|
5851
|
+
/** @const */ var PAYLOAD_TYPE_JSON = 'json';
|
|
6407
5852
|
|
|
6408
5853
|
|
|
6409
5854
|
/*
|
|
@@ -6434,8 +5879,8 @@
|
|
|
6434
5879
|
'api_host': 'https://api-js.mixpanel.com',
|
|
6435
5880
|
'api_method': 'POST',
|
|
6436
5881
|
'api_transport': 'XHR',
|
|
5882
|
+
'api_payload_format': PAYLOAD_TYPE_BASE64,
|
|
6437
5883
|
'app_host': 'https://mixpanel.com',
|
|
6438
|
-
'autotrack': true,
|
|
6439
5884
|
'cdn': 'https://cdn.mxpnl.com',
|
|
6440
5885
|
'cross_site_cookie': false,
|
|
6441
5886
|
'cross_subdomain_cookie': true,
|
|
@@ -6466,7 +5911,7 @@
|
|
|
6466
5911
|
'inapp_protocol': '//',
|
|
6467
5912
|
'inapp_link_new_window': false,
|
|
6468
5913
|
'ignore_dnt': false,
|
|
6469
|
-
'batch_requests':
|
|
5914
|
+
'batch_requests': true,
|
|
6470
5915
|
'batch_size': 50,
|
|
6471
5916
|
'batch_flush_interval_ms': 5000,
|
|
6472
5917
|
'batch_request_timeout_ms': 90000,
|
|
@@ -6499,7 +5944,7 @@
|
|
|
6499
5944
|
instance = target;
|
|
6500
5945
|
} else {
|
|
6501
5946
|
if (target && !_.isArray(target)) {
|
|
6502
|
-
console
|
|
5947
|
+
console.error('You have already initialized ' + name);
|
|
6503
5948
|
return;
|
|
6504
5949
|
}
|
|
6505
5950
|
instance = new MixpanelLib();
|
|
@@ -6518,21 +5963,6 @@
|
|
|
6518
5963
|
// global debug to be true
|
|
6519
5964
|
Config.DEBUG = Config.DEBUG || instance.get_config('debug');
|
|
6520
5965
|
|
|
6521
|
-
instance['__autotrack_enabled'] = instance.get_config('autotrack');
|
|
6522
|
-
if (instance.get_config('autotrack')) {
|
|
6523
|
-
var num_buckets = 100;
|
|
6524
|
-
var num_enabled_buckets = 100;
|
|
6525
|
-
if (!autotrack.enabledForProject(instance.get_config('token'), num_buckets, num_enabled_buckets)) {
|
|
6526
|
-
instance['__autotrack_enabled'] = false;
|
|
6527
|
-
console$1.log('Not in active bucket: disabling Automatic Event Collection.');
|
|
6528
|
-
} else if (!autotrack.isBrowserSupported()) {
|
|
6529
|
-
instance['__autotrack_enabled'] = false;
|
|
6530
|
-
console$1.log('Disabling Automatic Event Collection because this browser is not supported');
|
|
6531
|
-
} else {
|
|
6532
|
-
autotrack.init(instance);
|
|
6533
|
-
}
|
|
6534
|
-
}
|
|
6535
|
-
|
|
6536
5966
|
// if target is not defined, we called init after the lib already
|
|
6537
5967
|
// loaded, so there won't be an array of things to execute
|
|
6538
5968
|
if (!_.isUndefined(target) && _.isArray(target)) {
|
|
@@ -6545,12 +5975,6 @@
|
|
|
6545
5975
|
return instance;
|
|
6546
5976
|
};
|
|
6547
5977
|
|
|
6548
|
-
var encode_data_for_request = function(data) {
|
|
6549
|
-
var json_data = _.JSONEncode(data);
|
|
6550
|
-
var encoded_data = _.base64Encode(json_data);
|
|
6551
|
-
return {'data': encoded_data};
|
|
6552
|
-
};
|
|
6553
|
-
|
|
6554
5978
|
// Initialization methods
|
|
6555
5979
|
|
|
6556
5980
|
/**
|
|
@@ -6571,11 +5995,11 @@
|
|
|
6571
5995
|
*/
|
|
6572
5996
|
MixpanelLib.prototype.init = function (token, config, name) {
|
|
6573
5997
|
if (_.isUndefined(name)) {
|
|
6574
|
-
console
|
|
5998
|
+
console.error('You must name your new library: init(token, config, name)');
|
|
6575
5999
|
return;
|
|
6576
6000
|
}
|
|
6577
6001
|
if (name === PRIMARY_INSTANCE_NAME) {
|
|
6578
|
-
console
|
|
6002
|
+
console.error('You must initialize the main mixpanel object right after you include the Mixpanel js snippet');
|
|
6579
6003
|
return;
|
|
6580
6004
|
}
|
|
6581
6005
|
|
|
@@ -6600,14 +6024,14 @@
|
|
|
6600
6024
|
this['config'] = {};
|
|
6601
6025
|
this['_triggered_notifs'] = [];
|
|
6602
6026
|
|
|
6603
|
-
// rollout: enable batch_requests by default for 60% of projects
|
|
6604
|
-
// (only if they have not specified a value in their init config
|
|
6605
|
-
// and they aren't using a custom API host)
|
|
6606
6027
|
var variable_features = {};
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
if (!('
|
|
6610
|
-
|
|
6028
|
+
|
|
6029
|
+
// default to JSON payload for standard mixpanel.com API hosts
|
|
6030
|
+
if (!('api_payload_format' in config)) {
|
|
6031
|
+
var api_host = config['api_host'] || DEFAULT_CONFIG['api_host'];
|
|
6032
|
+
if (api_host.match(/\.mixpanel\.com$/)) {
|
|
6033
|
+
variable_features['api_payload_format'] = PAYLOAD_TYPE_JSON;
|
|
6034
|
+
}
|
|
6611
6035
|
}
|
|
6612
6036
|
|
|
6613
6037
|
this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
|
|
@@ -6632,19 +6056,36 @@
|
|
|
6632
6056
|
if (this._batch_requests) {
|
|
6633
6057
|
if (!_.localStorage.is_supported(true) || !USE_XHR) {
|
|
6634
6058
|
this._batch_requests = false;
|
|
6635
|
-
console
|
|
6059
|
+
console.log('Turning off Mixpanel request-queueing; needs XHR and localStorage support');
|
|
6636
6060
|
} else {
|
|
6637
6061
|
this.init_batchers();
|
|
6638
6062
|
if (sendBeacon && window$1.addEventListener) {
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6063
|
+
// Before page closes or hides (user tabs away etc), attempt to flush any events
|
|
6064
|
+
// queued up via navigator.sendBeacon. Since sendBeacon doesn't report success/failure,
|
|
6065
|
+
// events will not be removed from the persistent store; if the site is loaded again,
|
|
6066
|
+
// the events will be flushed again on startup and deduplicated on the Mixpanel server
|
|
6067
|
+
// side.
|
|
6068
|
+
// There is no reliable way to capture only page close events, so we lean on the
|
|
6069
|
+
// visibilitychange and pagehide events as recommended at
|
|
6070
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes.
|
|
6071
|
+
// These events fire when the user clicks away from the current page/tab, so will occur
|
|
6072
|
+
// more frequently than page unload, but are the only mechanism currently for capturing
|
|
6073
|
+
// this scenario somewhat reliably.
|
|
6074
|
+
var flush_on_unload = _.bind(function() {
|
|
6644
6075
|
if (!this.request_batchers.events.stopped) {
|
|
6645
6076
|
this.request_batchers.events.flush({unloading: true});
|
|
6646
6077
|
}
|
|
6647
|
-
}, this)
|
|
6078
|
+
}, this);
|
|
6079
|
+
window$1.addEventListener('pagehide', function(ev) {
|
|
6080
|
+
if (ev['persisted']) {
|
|
6081
|
+
flush_on_unload();
|
|
6082
|
+
}
|
|
6083
|
+
});
|
|
6084
|
+
window$1.addEventListener('visibilitychange', function() {
|
|
6085
|
+
if (document$1['visibilityState'] === 'hidden') {
|
|
6086
|
+
flush_on_unload();
|
|
6087
|
+
}
|
|
6088
|
+
});
|
|
6648
6089
|
}
|
|
6649
6090
|
}
|
|
6650
6091
|
}
|
|
@@ -6700,7 +6141,7 @@
|
|
|
6700
6141
|
|
|
6701
6142
|
MixpanelLib.prototype._track_dom = function(DomClass, args) {
|
|
6702
6143
|
if (this.get_config('img')) {
|
|
6703
|
-
console
|
|
6144
|
+
console.error('You can\'t use DOM tracking functions with img = true.');
|
|
6704
6145
|
return false;
|
|
6705
6146
|
}
|
|
6706
6147
|
|
|
@@ -6810,7 +6251,7 @@
|
|
|
6810
6251
|
try {
|
|
6811
6252
|
succeeded = sendBeacon(url, body_data);
|
|
6812
6253
|
} catch (e) {
|
|
6813
|
-
console
|
|
6254
|
+
console.error(e);
|
|
6814
6255
|
succeeded = false;
|
|
6815
6256
|
}
|
|
6816
6257
|
try {
|
|
@@ -6818,7 +6259,7 @@
|
|
|
6818
6259
|
callback(succeeded ? 1 : 0);
|
|
6819
6260
|
}
|
|
6820
6261
|
} catch (e) {
|
|
6821
|
-
console
|
|
6262
|
+
console.error(e);
|
|
6822
6263
|
}
|
|
6823
6264
|
} else if (USE_XHR) {
|
|
6824
6265
|
try {
|
|
@@ -6850,7 +6291,7 @@
|
|
|
6850
6291
|
try {
|
|
6851
6292
|
response = _.JSONDecode(req.responseText);
|
|
6852
6293
|
} catch (e) {
|
|
6853
|
-
console
|
|
6294
|
+
console.error(e);
|
|
6854
6295
|
if (options.ignore_json_errors) {
|
|
6855
6296
|
response = req.responseText;
|
|
6856
6297
|
} else {
|
|
@@ -6873,7 +6314,7 @@
|
|
|
6873
6314
|
} else {
|
|
6874
6315
|
error = 'Bad HTTP status: ' + req.status + ' ' + req.statusText;
|
|
6875
6316
|
}
|
|
6876
|
-
console
|
|
6317
|
+
console.error(error);
|
|
6877
6318
|
if (callback) {
|
|
6878
6319
|
if (verbose_mode) {
|
|
6879
6320
|
callback({status: 0, error: error, xhr_req: req});
|
|
@@ -6886,7 +6327,7 @@
|
|
|
6886
6327
|
};
|
|
6887
6328
|
req.send(body_data);
|
|
6888
6329
|
} catch (e) {
|
|
6889
|
-
console
|
|
6330
|
+
console.error(e);
|
|
6890
6331
|
succeeded = false;
|
|
6891
6332
|
}
|
|
6892
6333
|
} else {
|
|
@@ -6969,7 +6410,7 @@
|
|
|
6969
6410
|
sendRequestFunc: _.bind(function(data, options, cb) {
|
|
6970
6411
|
this._send_request(
|
|
6971
6412
|
this.get_config('api_host') + attrs.endpoint,
|
|
6972
|
-
|
|
6413
|
+
this._encode_data_for_request(data),
|
|
6973
6414
|
options,
|
|
6974
6415
|
this._prepare_callback(cb, data)
|
|
6975
6416
|
);
|
|
@@ -7043,6 +6484,14 @@
|
|
|
7043
6484
|
}
|
|
7044
6485
|
};
|
|
7045
6486
|
|
|
6487
|
+
MixpanelLib.prototype._encode_data_for_request = function(data) {
|
|
6488
|
+
var encoded_data = _.JSONEncode(data);
|
|
6489
|
+
if (this.get_config('api_payload_format') === PAYLOAD_TYPE_BASE64) {
|
|
6490
|
+
encoded_data = _.base64Encode(encoded_data);
|
|
6491
|
+
}
|
|
6492
|
+
return {'data': encoded_data};
|
|
6493
|
+
};
|
|
6494
|
+
|
|
7046
6495
|
// internal method for handling track vs batch-enqueue logic
|
|
7047
6496
|
MixpanelLib.prototype._track_or_batch = function(options, callback) {
|
|
7048
6497
|
var truncated_data = _.truncate(options.data, 255);
|
|
@@ -7058,11 +6507,11 @@
|
|
|
7058
6507
|
truncated_data = this._run_hook('before_send_' + options.type, truncated_data);
|
|
7059
6508
|
}
|
|
7060
6509
|
if (truncated_data) {
|
|
7061
|
-
console
|
|
7062
|
-
console
|
|
6510
|
+
console.log('MIXPANEL REQUEST:');
|
|
6511
|
+
console.log(truncated_data);
|
|
7063
6512
|
return this._send_request(
|
|
7064
6513
|
endpoint,
|
|
7065
|
-
|
|
6514
|
+
this._encode_data_for_request(truncated_data),
|
|
7066
6515
|
send_request_options,
|
|
7067
6516
|
this._prepare_callback(callback, truncated_data)
|
|
7068
6517
|
);
|
|
@@ -7125,7 +6574,7 @@
|
|
|
7125
6574
|
}
|
|
7126
6575
|
|
|
7127
6576
|
if (_.isUndefined(event_name)) {
|
|
7128
|
-
console
|
|
6577
|
+
console.error('No event name provided to mixpanel.track');
|
|
7129
6578
|
return;
|
|
7130
6579
|
}
|
|
7131
6580
|
|
|
@@ -7166,7 +6615,7 @@
|
|
|
7166
6615
|
delete properties[blacklisted_prop];
|
|
7167
6616
|
});
|
|
7168
6617
|
} else {
|
|
7169
|
-
console
|
|
6618
|
+
console.error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
7170
6619
|
}
|
|
7171
6620
|
|
|
7172
6621
|
var data = {
|
|
@@ -7411,7 +6860,7 @@
|
|
|
7411
6860
|
*/
|
|
7412
6861
|
MixpanelLib.prototype.time_event = function(event_name) {
|
|
7413
6862
|
if (_.isUndefined(event_name)) {
|
|
7414
|
-
console
|
|
6863
|
+
console.error('No event name provided to mixpanel.time_event');
|
|
7415
6864
|
return;
|
|
7416
6865
|
}
|
|
7417
6866
|
|
|
@@ -7684,7 +7133,7 @@
|
|
|
7684
7133
|
// mixpanel.people.identify() call made for this user. It is VERY BAD to make an alias with
|
|
7685
7134
|
// this ID, as it will duplicate users.
|
|
7686
7135
|
if (alias === this.get_property(PEOPLE_DISTINCT_ID_KEY)) {
|
|
7687
|
-
console
|
|
7136
|
+
console.critical('Attempting to create alias for existing People user - aborting.');
|
|
7688
7137
|
return -2;
|
|
7689
7138
|
}
|
|
7690
7139
|
|
|
@@ -7704,7 +7153,7 @@
|
|
|
7704
7153
|
_this.identify(alias);
|
|
7705
7154
|
});
|
|
7706
7155
|
} else {
|
|
7707
|
-
console
|
|
7156
|
+
console.error('alias matches current distinct_id - skipping api call.');
|
|
7708
7157
|
this.identify(alias);
|
|
7709
7158
|
return -1;
|
|
7710
7159
|
}
|
|
@@ -7887,7 +7336,7 @@
|
|
|
7887
7336
|
MixpanelLib.prototype._run_hook = function(hook_name) {
|
|
7888
7337
|
var ret = (this['config']['hooks'][hook_name] || IDENTITY_FUNC).apply(this, slice.call(arguments, 1));
|
|
7889
7338
|
if (typeof ret === 'undefined') {
|
|
7890
|
-
console
|
|
7339
|
+
console.error(hook_name + ' hook did not return a value');
|
|
7891
7340
|
ret = null;
|
|
7892
7341
|
}
|
|
7893
7342
|
return ret;
|
|
@@ -7953,7 +7402,7 @@
|
|
|
7953
7402
|
return;
|
|
7954
7403
|
}
|
|
7955
7404
|
|
|
7956
|
-
console
|
|
7405
|
+
console.log('MIXPANEL NOTIFICATION CHECK');
|
|
7957
7406
|
|
|
7958
7407
|
var data = {
|
|
7959
7408
|
'verbose': true,
|