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