mixpanel-browser 2.39.0 → 2.42.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 +21 -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 +370 -704
- package/dist/mixpanel.cjs.js +370 -704
- package/dist/mixpanel.globals.js +373 -707
- package/dist/mixpanel.min.js +150 -158
- package/dist/mixpanel.umd.js +370 -704
- package/doc/build-docs.js +16 -0
- package/doc/readme.io/javascript-full-api-reference.md +44 -2
- package/mixpanel-jslib-snippet.js +1 -20
- package/package.json +3 -3
- package/src/config.js +1 -1
- package/src/gdpr-utils.js +7 -2
- package/src/mixpanel-core.js +201 -85
- package/src/mixpanel-group.js +6 -1
- package/src/mixpanel-people.js +3 -3
- package/src/request-batcher.js +27 -13
- package/src/request-queue.js +47 -0
- package/src/utils.js +48 -37
- package/tunnel.log +0 -0
- package/.travis.yml +0 -8
- package/src/autotrack-utils.js +0 -192
- package/src/autotrack.js +0 -355
package/dist/mixpanel.umd.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
var Config = {
|
|
8
8
|
DEBUG: false,
|
|
9
|
-
LIB_VERSION: '2.
|
|
9
|
+
LIB_VERSION: '2.42.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) {
|
|
@@ -73,6 +73,19 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
/** @type {function(...*)} */
|
|
76
|
+
warn: function() {
|
|
77
|
+
if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
|
|
78
|
+
var args = ['Mixpanel warning:'].concat(_.toArray(arguments));
|
|
79
|
+
try {
|
|
80
|
+
windowConsole.warn.apply(windowConsole, args);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
_.each(args, function(arg) {
|
|
83
|
+
windowConsole.warn(arg);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
/** @type {function(...*)} */
|
|
76
89
|
error: function() {
|
|
77
90
|
if (Config.DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
|
|
78
91
|
var args = ['Mixpanel error:'].concat(_.toArray(arguments));
|
|
@@ -103,14 +116,14 @@
|
|
|
103
116
|
var log_func_with_prefix = function(func, prefix) {
|
|
104
117
|
return function() {
|
|
105
118
|
arguments[0] = '[' + prefix + '] ' + arguments[0];
|
|
106
|
-
return func.apply(console
|
|
119
|
+
return func.apply(console, arguments);
|
|
107
120
|
};
|
|
108
121
|
};
|
|
109
122
|
var console_with_prefix = function(prefix) {
|
|
110
123
|
return {
|
|
111
|
-
log: log_func_with_prefix(console
|
|
112
|
-
error: log_func_with_prefix(console
|
|
113
|
-
critical: log_func_with_prefix(console
|
|
124
|
+
log: log_func_with_prefix(console.log, prefix),
|
|
125
|
+
error: log_func_with_prefix(console.error, prefix),
|
|
126
|
+
critical: log_func_with_prefix(console.critical, prefix)
|
|
114
127
|
};
|
|
115
128
|
};
|
|
116
129
|
|
|
@@ -238,13 +251,13 @@
|
|
|
238
251
|
return _.values(iterable);
|
|
239
252
|
};
|
|
240
253
|
|
|
241
|
-
_.map = function(arr, callback) {
|
|
254
|
+
_.map = function(arr, callback, context) {
|
|
242
255
|
if (nativeMap && arr.map === nativeMap) {
|
|
243
|
-
return arr.map(callback);
|
|
256
|
+
return arr.map(callback, context);
|
|
244
257
|
} else {
|
|
245
258
|
var results = [];
|
|
246
259
|
_.each(arr, function(item) {
|
|
247
|
-
results.push(callback(item));
|
|
260
|
+
results.push(callback.call(context, item));
|
|
248
261
|
});
|
|
249
262
|
return results;
|
|
250
263
|
}
|
|
@@ -272,10 +285,6 @@
|
|
|
272
285
|
return results;
|
|
273
286
|
};
|
|
274
287
|
|
|
275
|
-
_.identity = function(value) {
|
|
276
|
-
return value;
|
|
277
|
-
};
|
|
278
|
-
|
|
279
288
|
_.include = function(obj, target) {
|
|
280
289
|
var found = false;
|
|
281
290
|
if (obj === null) {
|
|
@@ -376,9 +385,9 @@
|
|
|
376
385
|
try {
|
|
377
386
|
return f.apply(this, arguments);
|
|
378
387
|
} catch (e) {
|
|
379
|
-
console
|
|
388
|
+
console.critical('Implementation error. Please turn on debug and contact support@mixpanel.com.');
|
|
380
389
|
if (Config.DEBUG){
|
|
381
|
-
console
|
|
390
|
+
console.critical(e);
|
|
382
391
|
}
|
|
383
392
|
}
|
|
384
393
|
};
|
|
@@ -938,9 +947,37 @@
|
|
|
938
947
|
// _.isBlockedUA()
|
|
939
948
|
// This is to block various web spiders from executing our JS and
|
|
940
949
|
// sending false tracking data
|
|
950
|
+
var BLOCKED_UA_STRS = [
|
|
951
|
+
'baiduspider',
|
|
952
|
+
'bingbot',
|
|
953
|
+
'bingpreview',
|
|
954
|
+
'facebookexternal',
|
|
955
|
+
'pinterest',
|
|
956
|
+
'screaming frog',
|
|
957
|
+
'yahoo! slurp',
|
|
958
|
+
'yandexbot',
|
|
959
|
+
|
|
960
|
+
// a whole bunch of goog-specific crawlers
|
|
961
|
+
// https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers
|
|
962
|
+
'adsbot-google',
|
|
963
|
+
'apis-google',
|
|
964
|
+
'duplexweb-google',
|
|
965
|
+
'feedfetcher-google',
|
|
966
|
+
'google favicon',
|
|
967
|
+
'google web preview',
|
|
968
|
+
'google-read-aloud',
|
|
969
|
+
'googlebot',
|
|
970
|
+
'googleweblight',
|
|
971
|
+
'mediapartners-google',
|
|
972
|
+
'storebot-google'
|
|
973
|
+
];
|
|
941
974
|
_.isBlockedUA = function(ua) {
|
|
942
|
-
|
|
943
|
-
|
|
975
|
+
var i;
|
|
976
|
+
ua = ua.toLowerCase();
|
|
977
|
+
for (i = 0; i < BLOCKED_UA_STRS.length; i++) {
|
|
978
|
+
if (ua.indexOf(BLOCKED_UA_STRS[i]) !== -1) {
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
944
981
|
}
|
|
945
982
|
return false;
|
|
946
983
|
};
|
|
@@ -979,16 +1016,12 @@
|
|
|
979
1016
|
try {
|
|
980
1017
|
result = decodeURIComponent(result);
|
|
981
1018
|
} catch(err) {
|
|
982
|
-
console
|
|
1019
|
+
console.error('Skipping decoding for malformed query param: ' + result);
|
|
983
1020
|
}
|
|
984
1021
|
return result.replace(/\+/g, ' ');
|
|
985
1022
|
}
|
|
986
1023
|
};
|
|
987
1024
|
|
|
988
|
-
_.getHashParam = function(hash, param) {
|
|
989
|
-
var matches = hash.match(new RegExp(param + '=([^&]*)'));
|
|
990
|
-
return matches ? matches[1] : null;
|
|
991
|
-
};
|
|
992
1025
|
|
|
993
1026
|
// _.cookie
|
|
994
1027
|
// Methods partially borrowed from quirksmode.org/js/cookies.html
|
|
@@ -1110,13 +1143,13 @@
|
|
|
1110
1143
|
is_supported: function(force_check) {
|
|
1111
1144
|
var supported = localStorageSupported(null, force_check);
|
|
1112
1145
|
if (!supported) {
|
|
1113
|
-
console
|
|
1146
|
+
console.error('localStorage unsupported; falling back to cookie store');
|
|
1114
1147
|
}
|
|
1115
1148
|
return supported;
|
|
1116
1149
|
},
|
|
1117
1150
|
|
|
1118
1151
|
error: function(msg) {
|
|
1119
|
-
console
|
|
1152
|
+
console.error('localStorage error: ' + msg);
|
|
1120
1153
|
},
|
|
1121
1154
|
|
|
1122
1155
|
get: function(name) {
|
|
@@ -1171,7 +1204,7 @@
|
|
|
1171
1204
|
*/
|
|
1172
1205
|
var register_event = function(element, type, handler, oldSchool, useCapture) {
|
|
1173
1206
|
if (!element) {
|
|
1174
|
-
console
|
|
1207
|
+
console.error('No valid element provided to register_event');
|
|
1175
1208
|
return;
|
|
1176
1209
|
}
|
|
1177
1210
|
|
|
@@ -1655,28 +1688,6 @@
|
|
|
1655
1688
|
return maxlen ? guid.substring(0, maxlen) : guid;
|
|
1656
1689
|
};
|
|
1657
1690
|
|
|
1658
|
-
/**
|
|
1659
|
-
* Check deterministically whether to include or exclude from a feature rollout/test based on the
|
|
1660
|
-
* given string and the desired percentage to include.
|
|
1661
|
-
* @param {String} str - string to run the check against (for instance a project's token)
|
|
1662
|
-
* @param {String} feature - name of feature (for inclusion in hash, to ensure different results
|
|
1663
|
-
* for different features)
|
|
1664
|
-
* @param {Number} percent_allowed - percentage chance that a given string will be included
|
|
1665
|
-
* @returns {Boolean} whether the given string should be included
|
|
1666
|
-
*/
|
|
1667
|
-
var determine_eligibility = _.safewrap(function(str, feature, percent_allowed) {
|
|
1668
|
-
str = str + feature;
|
|
1669
|
-
|
|
1670
|
-
// Bernstein's hash: http://www.cse.yorku.ca/~oz/hash.html#djb2
|
|
1671
|
-
var hash = 5381;
|
|
1672
|
-
for (var i = 0; i < str.length; i++) {
|
|
1673
|
-
hash = ((hash << 5) + hash) + str.charCodeAt(i);
|
|
1674
|
-
hash = hash & hash;
|
|
1675
|
-
}
|
|
1676
|
-
var dart = (hash >>> 0) % 100;
|
|
1677
|
-
return dart < percent_allowed;
|
|
1678
|
-
});
|
|
1679
|
-
|
|
1680
1691
|
// naive way to extract domain name (example.com) from full hostname (my.sub.example.com)
|
|
1681
1692
|
var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i;
|
|
1682
1693
|
// this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk
|
|
@@ -1727,539 +1738,6 @@
|
|
|
1727
1738
|
_['info']['browserVersion'] = _.info.browserVersion;
|
|
1728
1739
|
_['info']['properties'] = _.info.properties;
|
|
1729
1740
|
|
|
1730
|
-
/*
|
|
1731
|
-
* Get the className of an element, accounting for edge cases where element.className is an object
|
|
1732
|
-
* @param {Element} el - element to get the className of
|
|
1733
|
-
* @returns {string} the element's class
|
|
1734
|
-
*/
|
|
1735
|
-
function getClassName(el) {
|
|
1736
|
-
switch(typeof el.className) {
|
|
1737
|
-
case 'string':
|
|
1738
|
-
return el.className;
|
|
1739
|
-
case 'object': // handle cases where className might be SVGAnimatedString or some other type
|
|
1740
|
-
return el.className.baseVal || el.getAttribute('class') || '';
|
|
1741
|
-
default: // future proof
|
|
1742
|
-
return '';
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
/*
|
|
1747
|
-
* Get the direct text content of an element, protecting against sensitive data collection.
|
|
1748
|
-
* Concats textContent of each of the element's text node children; this avoids potential
|
|
1749
|
-
* collection of sensitive data that could happen if we used element.textContent and the
|
|
1750
|
-
* element had sensitive child elements, since element.textContent includes child content.
|
|
1751
|
-
* Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
|
|
1752
|
-
* @param {Element} el - element to get the text of
|
|
1753
|
-
* @returns {string} the element's direct text content
|
|
1754
|
-
*/
|
|
1755
|
-
function getSafeText(el) {
|
|
1756
|
-
var elText = '';
|
|
1757
|
-
|
|
1758
|
-
if (shouldTrackElement(el) && el.childNodes && el.childNodes.length) {
|
|
1759
|
-
_.each(el.childNodes, function(child) {
|
|
1760
|
-
if (isTextNode(child) && child.textContent) {
|
|
1761
|
-
elText += _.trim(child.textContent)
|
|
1762
|
-
// scrub potentially sensitive values
|
|
1763
|
-
.split(/(\s+)/).filter(shouldTrackValue).join('')
|
|
1764
|
-
// normalize whitespace
|
|
1765
|
-
.replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ')
|
|
1766
|
-
// truncate
|
|
1767
|
-
.substring(0, 255);
|
|
1768
|
-
}
|
|
1769
|
-
});
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
return _.trim(elText);
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
|
-
/*
|
|
1776
|
-
* Check whether an element has nodeType Node.ELEMENT_NODE
|
|
1777
|
-
* @param {Element} el - element to check
|
|
1778
|
-
* @returns {boolean} whether el is of the correct nodeType
|
|
1779
|
-
*/
|
|
1780
|
-
function isElementNode(el) {
|
|
1781
|
-
return el && el.nodeType === 1; // Node.ELEMENT_NODE - use integer constant for browser portability
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
/*
|
|
1785
|
-
* Check whether an element is of a given tag type.
|
|
1786
|
-
* Due to potential reference discrepancies (such as the webcomponents.js polyfill),
|
|
1787
|
-
* we want to match tagNames instead of specific references because something like
|
|
1788
|
-
* element === document.body won't always work because element might not be a native
|
|
1789
|
-
* element.
|
|
1790
|
-
* @param {Element} el - element to check
|
|
1791
|
-
* @param {string} tag - tag name (e.g., "div")
|
|
1792
|
-
* @returns {boolean} whether el is of the given tag type
|
|
1793
|
-
*/
|
|
1794
|
-
function isTag(el, tag) {
|
|
1795
|
-
return el && el.tagName && el.tagName.toLowerCase() === tag.toLowerCase();
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
/*
|
|
1799
|
-
* Check whether an element has nodeType Node.TEXT_NODE
|
|
1800
|
-
* @param {Element} el - element to check
|
|
1801
|
-
* @returns {boolean} whether el is of the correct nodeType
|
|
1802
|
-
*/
|
|
1803
|
-
function isTextNode(el) {
|
|
1804
|
-
return el && el.nodeType === 3; // Node.TEXT_NODE - use integer constant for browser portability
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
/*
|
|
1808
|
-
* Check whether a DOM event should be "tracked" or if it may contain sentitive data
|
|
1809
|
-
* using a variety of heuristics.
|
|
1810
|
-
* @param {Element} el - element to check
|
|
1811
|
-
* @param {Event} event - event to check
|
|
1812
|
-
* @returns {boolean} whether the event should be tracked
|
|
1813
|
-
*/
|
|
1814
|
-
function shouldTrackDomEvent(el, event) {
|
|
1815
|
-
if (!el || isTag(el, 'html') || !isElementNode(el)) {
|
|
1816
|
-
return false;
|
|
1817
|
-
}
|
|
1818
|
-
var tag = el.tagName.toLowerCase();
|
|
1819
|
-
switch (tag) {
|
|
1820
|
-
case 'html':
|
|
1821
|
-
return false;
|
|
1822
|
-
case 'form':
|
|
1823
|
-
return event.type === 'submit';
|
|
1824
|
-
case 'input':
|
|
1825
|
-
if (['button', 'submit'].indexOf(el.getAttribute('type')) === -1) {
|
|
1826
|
-
return event.type === 'change';
|
|
1827
|
-
} else {
|
|
1828
|
-
return event.type === 'click';
|
|
1829
|
-
}
|
|
1830
|
-
case 'select':
|
|
1831
|
-
case 'textarea':
|
|
1832
|
-
return event.type === 'change';
|
|
1833
|
-
default:
|
|
1834
|
-
return event.type === 'click';
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
/*
|
|
1839
|
-
* Check whether a DOM element should be "tracked" or if it may contain sentitive data
|
|
1840
|
-
* using a variety of heuristics.
|
|
1841
|
-
* @param {Element} el - element to check
|
|
1842
|
-
* @returns {boolean} whether the element should be tracked
|
|
1843
|
-
*/
|
|
1844
|
-
function shouldTrackElement(el) {
|
|
1845
|
-
for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
|
|
1846
|
-
var classes = getClassName(curEl).split(' ');
|
|
1847
|
-
if (_.includes(classes, 'mp-sensitive') || _.includes(classes, 'mp-no-track')) {
|
|
1848
|
-
return false;
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
|
-
if (_.includes(getClassName(el).split(' '), 'mp-include')) {
|
|
1853
|
-
return true;
|
|
1854
|
-
}
|
|
1855
|
-
|
|
1856
|
-
// don't send data from inputs or similar elements since there will always be
|
|
1857
|
-
// a risk of clientside javascript placing sensitive data in attributes
|
|
1858
|
-
if (
|
|
1859
|
-
isTag(el, 'input') ||
|
|
1860
|
-
isTag(el, 'select') ||
|
|
1861
|
-
isTag(el, 'textarea') ||
|
|
1862
|
-
el.getAttribute('contenteditable') === 'true'
|
|
1863
|
-
) {
|
|
1864
|
-
return false;
|
|
1865
|
-
}
|
|
1866
|
-
|
|
1867
|
-
// don't include hidden or password fields
|
|
1868
|
-
var type = el.type || '';
|
|
1869
|
-
if (typeof type === 'string') { // it's possible for el.type to be a DOM element if el is a form with a child input[name="type"]
|
|
1870
|
-
switch(type.toLowerCase()) {
|
|
1871
|
-
case 'hidden':
|
|
1872
|
-
return false;
|
|
1873
|
-
case 'password':
|
|
1874
|
-
return false;
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
// filter out data from fields that look like sensitive fields
|
|
1879
|
-
var name = el.name || el.id || '';
|
|
1880
|
-
if (typeof name === 'string') { // it's possible for el.name or el.id to be a DOM element if el is a form with a child input[name="name"]
|
|
1881
|
-
var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
|
|
1882
|
-
if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
|
|
1883
|
-
return false;
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
return true;
|
|
1888
|
-
}
|
|
1889
|
-
|
|
1890
|
-
/*
|
|
1891
|
-
* Check whether a string value should be "tracked" or if it may contain sentitive data
|
|
1892
|
-
* using a variety of heuristics.
|
|
1893
|
-
* @param {string} value - string value to check
|
|
1894
|
-
* @returns {boolean} whether the element should be tracked
|
|
1895
|
-
*/
|
|
1896
|
-
function shouldTrackValue(value) {
|
|
1897
|
-
if (value === null || _.isUndefined(value)) {
|
|
1898
|
-
return false;
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
if (typeof value === 'string') {
|
|
1902
|
-
value = _.trim(value);
|
|
1903
|
-
|
|
1904
|
-
// check to see if input value looks like a credit card number
|
|
1905
|
-
// see: https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s20.html
|
|
1906
|
-
var ccRegex = /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/;
|
|
1907
|
-
if (ccRegex.test((value || '').replace(/[- ]/g, ''))) {
|
|
1908
|
-
return false;
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
// check to see if input value looks like a social security number
|
|
1912
|
-
var ssnRegex = /(^\d{3}-?\d{2}-?\d{4}$)/;
|
|
1913
|
-
if (ssnRegex.test(value)) {
|
|
1914
|
-
return false;
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
return true;
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
var autotrack = {
|
|
1922
|
-
_initializedTokens: [],
|
|
1923
|
-
|
|
1924
|
-
_previousElementSibling: function(el) {
|
|
1925
|
-
if (el.previousElementSibling) {
|
|
1926
|
-
return el.previousElementSibling;
|
|
1927
|
-
} else {
|
|
1928
|
-
do {
|
|
1929
|
-
el = el.previousSibling;
|
|
1930
|
-
} while (el && !isElementNode(el));
|
|
1931
|
-
return el;
|
|
1932
|
-
}
|
|
1933
|
-
},
|
|
1934
|
-
|
|
1935
|
-
_loadScript: function(scriptUrlToLoad, callback) {
|
|
1936
|
-
var scriptTag = document.createElement('script');
|
|
1937
|
-
scriptTag.type = 'text/javascript';
|
|
1938
|
-
scriptTag.src = scriptUrlToLoad;
|
|
1939
|
-
scriptTag.onload = callback;
|
|
1940
|
-
|
|
1941
|
-
var scripts = document.getElementsByTagName('script');
|
|
1942
|
-
if (scripts.length > 0) {
|
|
1943
|
-
scripts[0].parentNode.insertBefore(scriptTag, scripts[0]);
|
|
1944
|
-
} else {
|
|
1945
|
-
document.body.appendChild(scriptTag);
|
|
1946
|
-
}
|
|
1947
|
-
},
|
|
1948
|
-
|
|
1949
|
-
_getPropertiesFromElement: function(elem) {
|
|
1950
|
-
var props = {
|
|
1951
|
-
'classes': getClassName(elem).split(' '),
|
|
1952
|
-
'tag_name': elem.tagName.toLowerCase()
|
|
1953
|
-
};
|
|
1954
|
-
|
|
1955
|
-
if (shouldTrackElement(elem)) {
|
|
1956
|
-
_.each(elem.attributes, function(attr) {
|
|
1957
|
-
if (shouldTrackValue(attr.value)) {
|
|
1958
|
-
props['attr__' + attr.name] = attr.value;
|
|
1959
|
-
}
|
|
1960
|
-
});
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
|
-
var nthChild = 1;
|
|
1964
|
-
var nthOfType = 1;
|
|
1965
|
-
var currentElem = elem;
|
|
1966
|
-
while (currentElem = this._previousElementSibling(currentElem)) { // eslint-disable-line no-cond-assign
|
|
1967
|
-
nthChild++;
|
|
1968
|
-
if (currentElem.tagName === elem.tagName) {
|
|
1969
|
-
nthOfType++;
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
props['nth_child'] = nthChild;
|
|
1973
|
-
props['nth_of_type'] = nthOfType;
|
|
1974
|
-
|
|
1975
|
-
return props;
|
|
1976
|
-
},
|
|
1977
|
-
|
|
1978
|
-
_getDefaultProperties: function(eventType) {
|
|
1979
|
-
return {
|
|
1980
|
-
'$event_type': eventType,
|
|
1981
|
-
'$ce_version': 1,
|
|
1982
|
-
'$host': window.location.host,
|
|
1983
|
-
'$pathname': window.location.pathname
|
|
1984
|
-
};
|
|
1985
|
-
},
|
|
1986
|
-
|
|
1987
|
-
_extractCustomPropertyValue: function(customProperty) {
|
|
1988
|
-
var propValues = [];
|
|
1989
|
-
_.each(document.querySelectorAll(customProperty['css_selector']), function(matchedElem) {
|
|
1990
|
-
var value;
|
|
1991
|
-
|
|
1992
|
-
if (['input', 'select'].indexOf(matchedElem.tagName.toLowerCase()) > -1) {
|
|
1993
|
-
value = matchedElem['value'];
|
|
1994
|
-
} else if (matchedElem['textContent']) {
|
|
1995
|
-
value = matchedElem['textContent'];
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
if (shouldTrackValue(value)) {
|
|
1999
|
-
propValues.push(value);
|
|
2000
|
-
}
|
|
2001
|
-
});
|
|
2002
|
-
return propValues.join(', ');
|
|
2003
|
-
},
|
|
2004
|
-
|
|
2005
|
-
_getCustomProperties: function(targetElementList) {
|
|
2006
|
-
var props = {};
|
|
2007
|
-
_.each(this._customProperties, function(customProperty) {
|
|
2008
|
-
_.each(customProperty['event_selectors'], function(eventSelector) {
|
|
2009
|
-
var eventElements = document.querySelectorAll(eventSelector);
|
|
2010
|
-
_.each(eventElements, function(eventElement) {
|
|
2011
|
-
if (_.includes(targetElementList, eventElement) && shouldTrackElement(eventElement)) {
|
|
2012
|
-
props[customProperty['name']] = this._extractCustomPropertyValue(customProperty);
|
|
2013
|
-
}
|
|
2014
|
-
}, this);
|
|
2015
|
-
}, this);
|
|
2016
|
-
}, this);
|
|
2017
|
-
return props;
|
|
2018
|
-
},
|
|
2019
|
-
|
|
2020
|
-
_getEventTarget: function(e) {
|
|
2021
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/Event/target#Compatibility_notes
|
|
2022
|
-
if (typeof e.target === 'undefined') {
|
|
2023
|
-
return e.srcElement;
|
|
2024
|
-
} else {
|
|
2025
|
-
return e.target;
|
|
2026
|
-
}
|
|
2027
|
-
},
|
|
2028
|
-
|
|
2029
|
-
_trackEvent: function(e, instance) {
|
|
2030
|
-
/*** Don't mess with this code without running IE8 tests on it ***/
|
|
2031
|
-
var target = this._getEventTarget(e);
|
|
2032
|
-
if (isTextNode(target)) { // defeat Safari bug (see: http://www.quirksmode.org/js/events_properties.html)
|
|
2033
|
-
target = target.parentNode;
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
|
-
if (shouldTrackDomEvent(target, e)) {
|
|
2037
|
-
var targetElementList = [target];
|
|
2038
|
-
var curEl = target;
|
|
2039
|
-
while (curEl.parentNode && !isTag(curEl, 'body')) {
|
|
2040
|
-
targetElementList.push(curEl.parentNode);
|
|
2041
|
-
curEl = curEl.parentNode;
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
var elementsJson = [];
|
|
2045
|
-
var href, explicitNoTrack = false;
|
|
2046
|
-
_.each(targetElementList, function(el) {
|
|
2047
|
-
var shouldTrackEl = shouldTrackElement(el);
|
|
2048
|
-
|
|
2049
|
-
// if the element or a parent element is an anchor tag
|
|
2050
|
-
// include the href as a property
|
|
2051
|
-
if (el.tagName.toLowerCase() === 'a') {
|
|
2052
|
-
href = el.getAttribute('href');
|
|
2053
|
-
href = shouldTrackEl && shouldTrackValue(href) && href;
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
|
-
// allow users to programatically prevent tracking of elements by adding class 'mp-no-track'
|
|
2057
|
-
var classes = getClassName(el).split(' ');
|
|
2058
|
-
if (_.includes(classes, 'mp-no-track')) {
|
|
2059
|
-
explicitNoTrack = true;
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
elementsJson.push(this._getPropertiesFromElement(el));
|
|
2063
|
-
}, this);
|
|
2064
|
-
|
|
2065
|
-
if (explicitNoTrack) {
|
|
2066
|
-
return false;
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
// only populate text content from target element (not parents)
|
|
2070
|
-
// to prevent text within a sensitive element from being collected
|
|
2071
|
-
// as part of a parent's el.textContent
|
|
2072
|
-
var elementText;
|
|
2073
|
-
var safeElementText = getSafeText(target);
|
|
2074
|
-
if (safeElementText && safeElementText.length) {
|
|
2075
|
-
elementText = safeElementText;
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
var props = _.extend(
|
|
2079
|
-
this._getDefaultProperties(e.type),
|
|
2080
|
-
{
|
|
2081
|
-
'$elements': elementsJson,
|
|
2082
|
-
'$el_attr__href': href,
|
|
2083
|
-
'$el_text': elementText
|
|
2084
|
-
},
|
|
2085
|
-
this._getCustomProperties(targetElementList)
|
|
2086
|
-
);
|
|
2087
|
-
|
|
2088
|
-
instance.track('$web_event', props);
|
|
2089
|
-
return true;
|
|
2090
|
-
}
|
|
2091
|
-
},
|
|
2092
|
-
|
|
2093
|
-
// only reason is to stub for unit tests
|
|
2094
|
-
// since you can't override window.location props
|
|
2095
|
-
_navigate: function(href) {
|
|
2096
|
-
window.location.href = href;
|
|
2097
|
-
},
|
|
2098
|
-
|
|
2099
|
-
_addDomEventHandlers: function(instance) {
|
|
2100
|
-
var handler = _.bind(function(e) {
|
|
2101
|
-
e = e || window.event;
|
|
2102
|
-
this._trackEvent(e, instance);
|
|
2103
|
-
}, this);
|
|
2104
|
-
_.register_event(document, 'submit', handler, false, true);
|
|
2105
|
-
_.register_event(document, 'change', handler, false, true);
|
|
2106
|
-
_.register_event(document, 'click', handler, false, true);
|
|
2107
|
-
},
|
|
2108
|
-
|
|
2109
|
-
_customProperties: {},
|
|
2110
|
-
init: function(instance) {
|
|
2111
|
-
if (!(document && document.body)) {
|
|
2112
|
-
console.log('document not ready yet, trying again in 500 milliseconds...');
|
|
2113
|
-
var that = this;
|
|
2114
|
-
setTimeout(function() { that.init(instance); }, 500);
|
|
2115
|
-
return;
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
var token = instance.get_config('token');
|
|
2119
|
-
if (this._initializedTokens.indexOf(token) > -1) {
|
|
2120
|
-
console.log('autotrack already initialized for token "' + token + '"');
|
|
2121
|
-
return;
|
|
2122
|
-
}
|
|
2123
|
-
this._initializedTokens.push(token);
|
|
2124
|
-
|
|
2125
|
-
if (!this._maybeLoadEditor(instance)) { // don't autotrack actions when the editor is enabled
|
|
2126
|
-
var parseDecideResponse = _.bind(function(response) {
|
|
2127
|
-
if (response && response['config'] && response['config']['enable_collect_everything'] === true) {
|
|
2128
|
-
|
|
2129
|
-
if (response['custom_properties']) {
|
|
2130
|
-
this._customProperties = response['custom_properties'];
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
instance.track('$web_event', _.extend({
|
|
2134
|
-
'$title': document.title
|
|
2135
|
-
}, this._getDefaultProperties('pageview')));
|
|
2136
|
-
|
|
2137
|
-
this._addDomEventHandlers(instance);
|
|
2138
|
-
|
|
2139
|
-
} else {
|
|
2140
|
-
instance['__autotrack_enabled'] = false;
|
|
2141
|
-
}
|
|
2142
|
-
}, this);
|
|
2143
|
-
|
|
2144
|
-
instance._send_request(
|
|
2145
|
-
instance.get_config('api_host') + '/decide/', {
|
|
2146
|
-
'verbose': true,
|
|
2147
|
-
'version': '1',
|
|
2148
|
-
'lib': 'web',
|
|
2149
|
-
'token': token
|
|
2150
|
-
},
|
|
2151
|
-
{method: 'GET', transport: 'XHR'},
|
|
2152
|
-
instance._prepare_callback(parseDecideResponse)
|
|
2153
|
-
);
|
|
2154
|
-
}
|
|
2155
|
-
},
|
|
2156
|
-
|
|
2157
|
-
_editorParamsFromHash: function(instance, hash) {
|
|
2158
|
-
var editorParams;
|
|
2159
|
-
try {
|
|
2160
|
-
var state = _.getHashParam(hash, 'state');
|
|
2161
|
-
state = JSON.parse(decodeURIComponent(state));
|
|
2162
|
-
var expiresInSeconds = _.getHashParam(hash, 'expires_in');
|
|
2163
|
-
editorParams = {
|
|
2164
|
-
'accessToken': _.getHashParam(hash, 'access_token'),
|
|
2165
|
-
'accessTokenExpiresAt': (new Date()).getTime() + (Number(expiresInSeconds) * 1000),
|
|
2166
|
-
'bookmarkletMode': !!state['bookmarkletMode'],
|
|
2167
|
-
'projectId': state['projectId'],
|
|
2168
|
-
'projectOwnerId': state['projectOwnerId'],
|
|
2169
|
-
'projectToken': state['token'],
|
|
2170
|
-
'readOnly': state['readOnly'],
|
|
2171
|
-
'userFlags': state['userFlags'],
|
|
2172
|
-
'userId': state['userId']
|
|
2173
|
-
};
|
|
2174
|
-
window.sessionStorage.setItem('editorParams', JSON.stringify(editorParams));
|
|
2175
|
-
|
|
2176
|
-
if (state['desiredHash']) {
|
|
2177
|
-
window.location.hash = state['desiredHash'];
|
|
2178
|
-
} else if (window.history) {
|
|
2179
|
-
history.replaceState('', document.title, window.location.pathname + window.location.search); // completely remove hash
|
|
2180
|
-
} else {
|
|
2181
|
-
window.location.hash = ''; // clear hash (but leaves # unfortunately)
|
|
2182
|
-
}
|
|
2183
|
-
} catch (e) {
|
|
2184
|
-
console.error('Unable to parse data from hash', e);
|
|
2185
|
-
}
|
|
2186
|
-
return editorParams;
|
|
2187
|
-
},
|
|
2188
|
-
|
|
2189
|
-
/**
|
|
2190
|
-
* To load the visual editor, we need an access token and other state. That state comes from one of three places:
|
|
2191
|
-
* 1. In the URL hash params if the customer is using an old snippet
|
|
2192
|
-
* 2. From session storage under the key `_mpcehash` if the snippet already parsed the hash
|
|
2193
|
-
* 3. From session storage under the key `editorParams` if the editor was initialized on a previous page
|
|
2194
|
-
*/
|
|
2195
|
-
_maybeLoadEditor: function(instance) {
|
|
2196
|
-
try {
|
|
2197
|
-
var parseFromUrl = false;
|
|
2198
|
-
if (_.getHashParam(window.location.hash, 'state')) {
|
|
2199
|
-
var state = _.getHashParam(window.location.hash, 'state');
|
|
2200
|
-
state = JSON.parse(decodeURIComponent(state));
|
|
2201
|
-
parseFromUrl = state['action'] === 'mpeditor';
|
|
2202
|
-
}
|
|
2203
|
-
var parseFromStorage = !!window.sessionStorage.getItem('_mpcehash');
|
|
2204
|
-
var editorParams;
|
|
2205
|
-
|
|
2206
|
-
if (parseFromUrl) { // happens if they are initializing the editor using an old snippet
|
|
2207
|
-
editorParams = this._editorParamsFromHash(instance, window.location.hash);
|
|
2208
|
-
} else if (parseFromStorage) { // happens if they are initialized the editor and using the new snippet
|
|
2209
|
-
editorParams = this._editorParamsFromHash(instance, window.sessionStorage.getItem('_mpcehash'));
|
|
2210
|
-
window.sessionStorage.removeItem('_mpcehash');
|
|
2211
|
-
} else { // get credentials from sessionStorage from a previous initialzation
|
|
2212
|
-
editorParams = JSON.parse(window.sessionStorage.getItem('editorParams') || '{}');
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
if (editorParams['projectToken'] && instance.get_config('token') === editorParams['projectToken']) {
|
|
2216
|
-
this._loadEditor(instance, editorParams);
|
|
2217
|
-
return true;
|
|
2218
|
-
} else {
|
|
2219
|
-
return false;
|
|
2220
|
-
}
|
|
2221
|
-
} catch (e) {
|
|
2222
|
-
return false;
|
|
2223
|
-
}
|
|
2224
|
-
},
|
|
2225
|
-
|
|
2226
|
-
_loadEditor: function(instance, editorParams) {
|
|
2227
|
-
if (!window['_mpEditorLoaded']) { // only load the codeless event editor once, even if there are multiple instances of MixpanelLib
|
|
2228
|
-
window['_mpEditorLoaded'] = true;
|
|
2229
|
-
var editorUrl = instance.get_config('app_host')
|
|
2230
|
-
+ '/js-bundle/reports/collect-everything/editor.js?_ts='
|
|
2231
|
-
+ (new Date()).getTime();
|
|
2232
|
-
this._loadScript(editorUrl, function() {
|
|
2233
|
-
window['mp_load_editor'](editorParams);
|
|
2234
|
-
});
|
|
2235
|
-
return true;
|
|
2236
|
-
}
|
|
2237
|
-
return false;
|
|
2238
|
-
},
|
|
2239
|
-
|
|
2240
|
-
// this is a mechanism to ramp up CE with no server-side interaction.
|
|
2241
|
-
// when CE is active, every page load results in a decide request. we
|
|
2242
|
-
// need to gently ramp this up so we don't overload decide. this decides
|
|
2243
|
-
// deterministically if CE is enabled for this project by modding the char
|
|
2244
|
-
// value of the project token.
|
|
2245
|
-
enabledForProject: function(token, numBuckets, numEnabledBuckets) {
|
|
2246
|
-
numBuckets = !_.isUndefined(numBuckets) ? numBuckets : 10;
|
|
2247
|
-
numEnabledBuckets = !_.isUndefined(numEnabledBuckets) ? numEnabledBuckets : 10;
|
|
2248
|
-
var charCodeSum = 0;
|
|
2249
|
-
for (var i = 0; i < token.length; i++) {
|
|
2250
|
-
charCodeSum += token.charCodeAt(i);
|
|
2251
|
-
}
|
|
2252
|
-
return (charCodeSum % numBuckets) < numEnabledBuckets;
|
|
2253
|
-
},
|
|
2254
|
-
|
|
2255
|
-
isBrowserSupported: function() {
|
|
2256
|
-
return _.isFunction(document.querySelectorAll);
|
|
2257
|
-
}
|
|
2258
|
-
};
|
|
2259
|
-
|
|
2260
|
-
_.bind_instance_methods(autotrack);
|
|
2261
|
-
_.safewrap_instance_methods(autotrack);
|
|
2262
|
-
|
|
2263
1741
|
/**
|
|
2264
1742
|
* DomTracker Object
|
|
2265
1743
|
* @constructor
|
|
@@ -2288,7 +1766,7 @@
|
|
|
2288
1766
|
var elements = _.dom_query(query);
|
|
2289
1767
|
|
|
2290
1768
|
if (elements.length === 0) {
|
|
2291
|
-
console
|
|
1769
|
+
console.error('The DOM query (' + query + ') returned 0 elements');
|
|
2292
1770
|
return;
|
|
2293
1771
|
}
|
|
2294
1772
|
|
|
@@ -2653,6 +2131,7 @@
|
|
|
2653
2131
|
for (var i = 0; i < storedQueue.length; i++) {
|
|
2654
2132
|
var item = storedQueue[i];
|
|
2655
2133
|
if (new Date().getTime() > item['flushAfter'] && !idsInBatch[item['id']]) {
|
|
2134
|
+
item.orphaned = true;
|
|
2656
2135
|
batch.push(item);
|
|
2657
2136
|
if (batch.length >= batchSize) {
|
|
2658
2137
|
break;
|
|
@@ -2709,6 +2188,52 @@
|
|
|
2709
2188
|
}, this.pid);
|
|
2710
2189
|
};
|
|
2711
2190
|
|
|
2191
|
+
// internal helper for RequestQueue.updatePayloads
|
|
2192
|
+
var updatePayloads = function(existingItems, itemsToUpdate) {
|
|
2193
|
+
var newItems = [];
|
|
2194
|
+
_.each(existingItems, function(item) {
|
|
2195
|
+
var id = item['id'];
|
|
2196
|
+
if (id in itemsToUpdate) {
|
|
2197
|
+
var newPayload = itemsToUpdate[id];
|
|
2198
|
+
if (newPayload !== null) {
|
|
2199
|
+
item['payload'] = newPayload;
|
|
2200
|
+
newItems.push(item);
|
|
2201
|
+
}
|
|
2202
|
+
} else {
|
|
2203
|
+
// no update
|
|
2204
|
+
newItems.push(item);
|
|
2205
|
+
}
|
|
2206
|
+
});
|
|
2207
|
+
return newItems;
|
|
2208
|
+
};
|
|
2209
|
+
|
|
2210
|
+
/**
|
|
2211
|
+
* Update payloads of given items in both in-memory queue and
|
|
2212
|
+
* persisted queue. Items set to null are removed from queues.
|
|
2213
|
+
*/
|
|
2214
|
+
RequestQueue.prototype.updatePayloads = function(itemsToUpdate, cb) {
|
|
2215
|
+
this.memQueue = updatePayloads(this.memQueue, itemsToUpdate);
|
|
2216
|
+
this.lock.withLock(_.bind(function lockAcquired() {
|
|
2217
|
+
var succeeded;
|
|
2218
|
+
try {
|
|
2219
|
+
var storedQueue = this.readFromStorage();
|
|
2220
|
+
storedQueue = updatePayloads(storedQueue, itemsToUpdate);
|
|
2221
|
+
succeeded = this.saveToStorage(storedQueue);
|
|
2222
|
+
} catch(err) {
|
|
2223
|
+
logger$1.error('Error updating items', itemsToUpdate);
|
|
2224
|
+
succeeded = false;
|
|
2225
|
+
}
|
|
2226
|
+
if (cb) {
|
|
2227
|
+
cb(succeeded);
|
|
2228
|
+
}
|
|
2229
|
+
}, this), function lockFailure(err) {
|
|
2230
|
+
logger$1.error('Error acquiring storage lock', err);
|
|
2231
|
+
if (cb) {
|
|
2232
|
+
cb(false);
|
|
2233
|
+
}
|
|
2234
|
+
}, this.pid);
|
|
2235
|
+
};
|
|
2236
|
+
|
|
2712
2237
|
/**
|
|
2713
2238
|
* Read and parse items array from localStorage entry, handling
|
|
2714
2239
|
* malformed/missing data if necessary.
|
|
@@ -2765,18 +2290,18 @@
|
|
|
2765
2290
|
* Uses RequestQueue to manage the backing store.
|
|
2766
2291
|
* @constructor
|
|
2767
2292
|
*/
|
|
2768
|
-
var RequestBatcher = function(storageKey,
|
|
2293
|
+
var RequestBatcher = function(storageKey, options) {
|
|
2769
2294
|
this.queue = new RequestQueue(storageKey, {storage: options.storage});
|
|
2770
|
-
this.endpoint = endpoint;
|
|
2771
2295
|
|
|
2772
2296
|
this.libConfig = options.libConfig;
|
|
2773
2297
|
this.sendRequest = options.sendRequestFunc;
|
|
2298
|
+
this.beforeSendHook = options.beforeSendHook;
|
|
2774
2299
|
|
|
2775
2300
|
// seed variable batch size + flush interval with configured values
|
|
2776
2301
|
this.batchSize = this.libConfig['batch_size'];
|
|
2777
2302
|
this.flushInterval = this.libConfig['batch_flush_interval_ms'];
|
|
2778
2303
|
|
|
2779
|
-
this.stopped =
|
|
2304
|
+
this.stopped = !this.libConfig['batch_autostart'];
|
|
2780
2305
|
};
|
|
2781
2306
|
|
|
2782
2307
|
/**
|
|
@@ -2856,18 +2381,29 @@
|
|
|
2856
2381
|
}
|
|
2857
2382
|
|
|
2858
2383
|
options = options || {};
|
|
2384
|
+
var timeoutMS = this.libConfig['batch_request_timeout_ms'];
|
|
2385
|
+
var startTime = new Date().getTime();
|
|
2859
2386
|
var currentBatchSize = this.batchSize;
|
|
2860
2387
|
var batch = this.queue.fillBatch(currentBatchSize);
|
|
2861
|
-
|
|
2388
|
+
var dataForRequest = [];
|
|
2389
|
+
var transformedItems = {};
|
|
2390
|
+
_.each(batch, function(item) {
|
|
2391
|
+
var payload = item['payload'];
|
|
2392
|
+
if (this.beforeSendHook && !item.orphaned) {
|
|
2393
|
+
payload = this.beforeSendHook(payload);
|
|
2394
|
+
}
|
|
2395
|
+
if (payload) {
|
|
2396
|
+
dataForRequest.push(payload);
|
|
2397
|
+
}
|
|
2398
|
+
transformedItems[item['id']] = payload;
|
|
2399
|
+
}, this);
|
|
2400
|
+
if (dataForRequest.length < 1) {
|
|
2862
2401
|
this.resetFlush();
|
|
2863
2402
|
return; // nothing to do
|
|
2864
2403
|
}
|
|
2865
2404
|
|
|
2866
2405
|
this.requestInProgress = true;
|
|
2867
2406
|
|
|
2868
|
-
var timeoutMS = this.libConfig['batch_request_timeout_ms'];
|
|
2869
|
-
var startTime = new Date().getTime();
|
|
2870
|
-
var dataForRequest = _.map(batch, function(item) { return item['payload']; });
|
|
2871
2407
|
var batchSendCallback = _.bind(function(res) {
|
|
2872
2408
|
this.requestInProgress = false;
|
|
2873
2409
|
|
|
@@ -2877,7 +2413,10 @@
|
|
|
2877
2413
|
// flush operation if something goes wrong
|
|
2878
2414
|
|
|
2879
2415
|
var removeItemsFromQueue = false;
|
|
2880
|
-
if (
|
|
2416
|
+
if (options.unloading) {
|
|
2417
|
+
// update persisted data to include hook transformations
|
|
2418
|
+
this.queue.updatePayloads(transformedItems);
|
|
2419
|
+
} else if (
|
|
2881
2420
|
_.isObject(res) &&
|
|
2882
2421
|
res.error === 'timeout' &&
|
|
2883
2422
|
new Date().getTime() - startTime >= timeoutMS
|
|
@@ -2887,9 +2426,9 @@
|
|
|
2887
2426
|
} else if (
|
|
2888
2427
|
_.isObject(res) &&
|
|
2889
2428
|
res.xhr_req &&
|
|
2890
|
-
(res.xhr_req['status'] >= 500 || res.xhr_req['status']
|
|
2429
|
+
(res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout')
|
|
2891
2430
|
) {
|
|
2892
|
-
// network or API error, retry
|
|
2431
|
+
// network or API error, or 429 Too Many Requests, retry
|
|
2893
2432
|
var retryMS = this.flushInterval * 2;
|
|
2894
2433
|
var headers = res.xhr_req['responseHeaders'];
|
|
2895
2434
|
if (headers) {
|
|
@@ -2937,11 +2476,11 @@
|
|
|
2937
2476
|
ignore_json_errors: true, // eslint-disable-line camelcase
|
|
2938
2477
|
timeout_ms: timeoutMS // eslint-disable-line camelcase
|
|
2939
2478
|
};
|
|
2940
|
-
if (options.
|
|
2479
|
+
if (options.unloading) {
|
|
2941
2480
|
requestOptions.transport = 'sendBeacon';
|
|
2942
2481
|
}
|
|
2943
|
-
logger.log('MIXPANEL REQUEST:',
|
|
2944
|
-
this.sendRequest(
|
|
2482
|
+
logger.log('MIXPANEL REQUEST:', dataForRequest);
|
|
2483
|
+
this.sendRequest(dataForRequest, requestOptions, batchSendCallback);
|
|
2945
2484
|
|
|
2946
2485
|
} catch(err) {
|
|
2947
2486
|
logger.error('Error flushing request queue', err);
|
|
@@ -3019,9 +2558,14 @@
|
|
|
3019
2558
|
*/
|
|
3020
2559
|
function hasOptedOut(token, options) {
|
|
3021
2560
|
if (_hasDoNotTrackFlagOn(options)) {
|
|
2561
|
+
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"');
|
|
3022
2562
|
return true;
|
|
3023
2563
|
}
|
|
3024
|
-
|
|
2564
|
+
var optedOut = _getStorageValue(token, options) === '0';
|
|
2565
|
+
if (optedOut) {
|
|
2566
|
+
console.warn('You are opted out of Mixpanel tracking. This will prevent the Mixpanel SDK from sending any data.');
|
|
2567
|
+
}
|
|
2568
|
+
return optedOut;
|
|
3025
2569
|
}
|
|
3026
2570
|
|
|
3027
2571
|
/**
|
|
@@ -3454,9 +2998,13 @@
|
|
|
3454
2998
|
* Permanently delete a group.
|
|
3455
2999
|
*
|
|
3456
3000
|
* ### Usage:
|
|
3001
|
+
*
|
|
3457
3002
|
* mixpanel.get_group('company', 'mixpanel').delete();
|
|
3003
|
+
*
|
|
3004
|
+
* @param {Function} [callback] If provided, the callback will be called after the tracking event
|
|
3458
3005
|
*/
|
|
3459
3006
|
MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) {
|
|
3007
|
+
// bracket notation above prevents a minification error related to reserved words
|
|
3460
3008
|
var data = this.delete_action();
|
|
3461
3009
|
return this._send_request(data, callback);
|
|
3462
3010
|
});
|
|
@@ -3484,7 +3032,8 @@
|
|
|
3484
3032
|
|
|
3485
3033
|
var date_encoded_data = _.encodeDates(data);
|
|
3486
3034
|
return this._mixpanel._track_or_batch({
|
|
3487
|
-
|
|
3035
|
+
type: 'groups',
|
|
3036
|
+
data: date_encoded_data,
|
|
3488
3037
|
endpoint: this._get_config('api_host') + '/groups/',
|
|
3489
3038
|
batcher: this._mixpanel.request_batchers.groups
|
|
3490
3039
|
}, callback);
|
|
@@ -3555,7 +3104,7 @@
|
|
|
3555
3104
|
|
|
3556
3105
|
var storage_type = config['persistence'];
|
|
3557
3106
|
if (storage_type !== 'cookie' && storage_type !== 'localStorage') {
|
|
3558
|
-
console
|
|
3107
|
+
console.critical('Unknown persistence type ' + storage_type + '; falling back to cookie');
|
|
3559
3108
|
storage_type = config['persistence'] = 'cookie';
|
|
3560
3109
|
}
|
|
3561
3110
|
|
|
@@ -3913,8 +3462,8 @@
|
|
|
3913
3462
|
this._pop_from_people_queue(UNSET_ACTION, q_data);
|
|
3914
3463
|
}
|
|
3915
3464
|
|
|
3916
|
-
console
|
|
3917
|
-
console
|
|
3465
|
+
console.log('MIXPANEL PEOPLE REQUEST (QUEUED, PENDING IDENTIFY):');
|
|
3466
|
+
console.log(data);
|
|
3918
3467
|
|
|
3919
3468
|
this.save();
|
|
3920
3469
|
};
|
|
@@ -3957,7 +3506,7 @@
|
|
|
3957
3506
|
} else if (queue === UNION_ACTION) {
|
|
3958
3507
|
return UNION_QUEUE_KEY;
|
|
3959
3508
|
} else {
|
|
3960
|
-
console
|
|
3509
|
+
console.error('Invalid queue:', queue);
|
|
3961
3510
|
}
|
|
3962
3511
|
};
|
|
3963
3512
|
|
|
@@ -5925,7 +5474,7 @@
|
|
|
5925
5474
|
_.each(prop, function(v, k) {
|
|
5926
5475
|
if (!this._is_reserved_property(k)) {
|
|
5927
5476
|
if (isNaN(parseFloat(v))) {
|
|
5928
|
-
console
|
|
5477
|
+
console.error('Invalid increment value passed to mixpanel.people.increment - must be a number');
|
|
5929
5478
|
return;
|
|
5930
5479
|
} else {
|
|
5931
5480
|
$add[k] = v;
|
|
@@ -6049,7 +5598,7 @@
|
|
|
6049
5598
|
if (!_.isNumber(amount)) {
|
|
6050
5599
|
amount = parseFloat(amount);
|
|
6051
5600
|
if (isNaN(amount)) {
|
|
6052
|
-
console
|
|
5601
|
+
console.error('Invalid value passed to mixpanel.people.track_charge - must be a number');
|
|
6053
5602
|
return;
|
|
6054
5603
|
}
|
|
6055
5604
|
}
|
|
@@ -6085,7 +5634,7 @@
|
|
|
6085
5634
|
*/
|
|
6086
5635
|
MixpanelPeople.prototype.delete_user = function() {
|
|
6087
5636
|
if (!this._identify_called()) {
|
|
6088
|
-
console
|
|
5637
|
+
console.error('mixpanel.people.delete_user() requires you to call identify() first');
|
|
6089
5638
|
return;
|
|
6090
5639
|
}
|
|
6091
5640
|
var data = {'$delete': this._mixpanel.get_distinct_id()};
|
|
@@ -6113,7 +5662,6 @@
|
|
|
6113
5662
|
}
|
|
6114
5663
|
|
|
6115
5664
|
var date_encoded_data = _.encodeDates(data);
|
|
6116
|
-
var truncated_data = _.truncate(date_encoded_data, 255);
|
|
6117
5665
|
|
|
6118
5666
|
if (!this._identify_called()) {
|
|
6119
5667
|
this._enqueue(data);
|
|
@@ -6124,11 +5672,12 @@
|
|
|
6124
5672
|
callback(-1);
|
|
6125
5673
|
}
|
|
6126
5674
|
}
|
|
6127
|
-
return
|
|
5675
|
+
return _.truncate(date_encoded_data, 255);
|
|
6128
5676
|
}
|
|
6129
5677
|
|
|
6130
5678
|
return this._mixpanel._track_or_batch({
|
|
6131
|
-
|
|
5679
|
+
type: 'people',
|
|
5680
|
+
data: date_encoded_data,
|
|
6132
5681
|
endpoint: this._get_config('api_host') + '/engage/',
|
|
6133
5682
|
batcher: this._mixpanel.request_batchers.people
|
|
6134
5683
|
}, callback);
|
|
@@ -6159,7 +5708,7 @@
|
|
|
6159
5708
|
} else if (UNION_ACTION in data) {
|
|
6160
5709
|
this._mixpanel['persistence']._add_to_people_queue(UNION_ACTION, data);
|
|
6161
5710
|
} else {
|
|
6162
|
-
console
|
|
5711
|
+
console.error('Invalid call to _enqueue():', data);
|
|
6163
5712
|
}
|
|
6164
5713
|
};
|
|
6165
5714
|
|
|
@@ -6292,6 +5841,9 @@
|
|
|
6292
5841
|
var INIT_MODULE = 0;
|
|
6293
5842
|
var INIT_SNIPPET = 1;
|
|
6294
5843
|
|
|
5844
|
+
var IDENTITY_FUNC = function(x) {return x;};
|
|
5845
|
+
var NOOP_FUNC = function() {};
|
|
5846
|
+
|
|
6295
5847
|
/** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
|
|
6296
5848
|
|
|
6297
5849
|
|
|
@@ -6324,7 +5876,6 @@
|
|
|
6324
5876
|
'api_method': 'POST',
|
|
6325
5877
|
'api_transport': 'XHR',
|
|
6326
5878
|
'app_host': 'https://mixpanel.com',
|
|
6327
|
-
'autotrack': true,
|
|
6328
5879
|
'cdn': 'https://cdn.mxpnl.com',
|
|
6329
5880
|
'cross_site_cookie': false,
|
|
6330
5881
|
'cross_subdomain_cookie': true,
|
|
@@ -6332,7 +5883,7 @@
|
|
|
6332
5883
|
'persistence_name': '',
|
|
6333
5884
|
'cookie_domain': '',
|
|
6334
5885
|
'cookie_name': '',
|
|
6335
|
-
'loaded':
|
|
5886
|
+
'loaded': NOOP_FUNC,
|
|
6336
5887
|
'store_google': true,
|
|
6337
5888
|
'save_referrer': true,
|
|
6338
5889
|
'test': false,
|
|
@@ -6355,10 +5906,12 @@
|
|
|
6355
5906
|
'inapp_protocol': '//',
|
|
6356
5907
|
'inapp_link_new_window': false,
|
|
6357
5908
|
'ignore_dnt': false,
|
|
6358
|
-
'batch_requests':
|
|
5909
|
+
'batch_requests': true,
|
|
6359
5910
|
'batch_size': 50,
|
|
6360
5911
|
'batch_flush_interval_ms': 5000,
|
|
6361
|
-
'batch_request_timeout_ms': 90000
|
|
5912
|
+
'batch_request_timeout_ms': 90000,
|
|
5913
|
+
'batch_autostart': true,
|
|
5914
|
+
'hooks': {}
|
|
6362
5915
|
};
|
|
6363
5916
|
|
|
6364
5917
|
var DOM_LOADED = false;
|
|
@@ -6386,7 +5939,7 @@
|
|
|
6386
5939
|
instance = target;
|
|
6387
5940
|
} else {
|
|
6388
5941
|
if (target && !_.isArray(target)) {
|
|
6389
|
-
console
|
|
5942
|
+
console.error('You have already initialized ' + name);
|
|
6390
5943
|
return;
|
|
6391
5944
|
}
|
|
6392
5945
|
instance = new MixpanelLib();
|
|
@@ -6405,21 +5958,6 @@
|
|
|
6405
5958
|
// global debug to be true
|
|
6406
5959
|
Config.DEBUG = Config.DEBUG || instance.get_config('debug');
|
|
6407
5960
|
|
|
6408
|
-
instance['__autotrack_enabled'] = instance.get_config('autotrack');
|
|
6409
|
-
if (instance.get_config('autotrack')) {
|
|
6410
|
-
var num_buckets = 100;
|
|
6411
|
-
var num_enabled_buckets = 100;
|
|
6412
|
-
if (!autotrack.enabledForProject(instance.get_config('token'), num_buckets, num_enabled_buckets)) {
|
|
6413
|
-
instance['__autotrack_enabled'] = false;
|
|
6414
|
-
console$1.log('Not in active bucket: disabling Automatic Event Collection.');
|
|
6415
|
-
} else if (!autotrack.isBrowserSupported()) {
|
|
6416
|
-
instance['__autotrack_enabled'] = false;
|
|
6417
|
-
console$1.log('Disabling Automatic Event Collection because this browser is not supported');
|
|
6418
|
-
} else {
|
|
6419
|
-
autotrack.init(instance);
|
|
6420
|
-
}
|
|
6421
|
-
}
|
|
6422
|
-
|
|
6423
5961
|
// if target is not defined, we called init after the lib already
|
|
6424
5962
|
// loaded, so there won't be an array of things to execute
|
|
6425
5963
|
if (!_.isUndefined(target) && _.isArray(target)) {
|
|
@@ -6458,11 +5996,11 @@
|
|
|
6458
5996
|
*/
|
|
6459
5997
|
MixpanelLib.prototype.init = function (token, config, name) {
|
|
6460
5998
|
if (_.isUndefined(name)) {
|
|
6461
|
-
console
|
|
5999
|
+
console.error('You must name your new library: init(token, config, name)');
|
|
6462
6000
|
return;
|
|
6463
6001
|
}
|
|
6464
6002
|
if (name === PRIMARY_INSTANCE_NAME) {
|
|
6465
|
-
console
|
|
6003
|
+
console.error('You must initialize the main mixpanel object right after you include the Mixpanel js snippet');
|
|
6466
6004
|
return;
|
|
6467
6005
|
}
|
|
6468
6006
|
|
|
@@ -6487,23 +6025,13 @@
|
|
|
6487
6025
|
this['config'] = {};
|
|
6488
6026
|
this['_triggered_notifs'] = [];
|
|
6489
6027
|
|
|
6490
|
-
|
|
6491
|
-
// (only if they have not specified a value in their init config
|
|
6492
|
-
// and they aren't using a custom API host)
|
|
6493
|
-
var variable_features = {};
|
|
6494
|
-
var api_host = config['api_host'];
|
|
6495
|
-
var is_custom_api = !!api_host && !api_host.match(/\.mixpanel\.com$/);
|
|
6496
|
-
if (!('batch_requests' in config) && !is_custom_api && determine_eligibility(token, 'batch', 30)) {
|
|
6497
|
-
variable_features['batch_requests'] = true;
|
|
6498
|
-
}
|
|
6499
|
-
|
|
6500
|
-
this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
|
|
6028
|
+
this.set_config(_.extend({}, DEFAULT_CONFIG, config, {
|
|
6501
6029
|
'name': name,
|
|
6502
6030
|
'token': token,
|
|
6503
6031
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
6504
6032
|
}));
|
|
6505
6033
|
|
|
6506
|
-
this['_jsc'] =
|
|
6034
|
+
this['_jsc'] = NOOP_FUNC;
|
|
6507
6035
|
|
|
6508
6036
|
this.__dom_loaded_queue = [];
|
|
6509
6037
|
this.__request_queue = [];
|
|
@@ -6519,22 +6047,42 @@
|
|
|
6519
6047
|
if (this._batch_requests) {
|
|
6520
6048
|
if (!_.localStorage.is_supported(true) || !USE_XHR) {
|
|
6521
6049
|
this._batch_requests = false;
|
|
6522
|
-
console
|
|
6050
|
+
console.log('Turning off Mixpanel request-queueing; needs XHR and localStorage support');
|
|
6523
6051
|
} else {
|
|
6524
|
-
this.
|
|
6052
|
+
this.init_batchers();
|
|
6525
6053
|
if (sendBeacon && window$1.addEventListener) {
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6054
|
+
// Before page closes or hides (user tabs away etc), attempt to flush any events
|
|
6055
|
+
// queued up via navigator.sendBeacon. Since sendBeacon doesn't report success/failure,
|
|
6056
|
+
// events will not be removed from the persistent store; if the site is loaded again,
|
|
6057
|
+
// the events will be flushed again on startup and deduplicated on the Mixpanel server
|
|
6058
|
+
// side.
|
|
6059
|
+
// There is no reliable way to capture only page close events, so we lean on the
|
|
6060
|
+
// visibilitychange and pagehide events as recommended at
|
|
6061
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes.
|
|
6062
|
+
// These events fire when the user clicks away from the current page/tab, so will occur
|
|
6063
|
+
// more frequently than page unload, but are the only mechanism currently for capturing
|
|
6064
|
+
// this scenario somewhat reliably.
|
|
6065
|
+
var flush_on_unload = _.bind(function() {
|
|
6066
|
+
if (!this.request_batchers.events.stopped) {
|
|
6067
|
+
this.request_batchers.events.flush({unloading: true});
|
|
6068
|
+
}
|
|
6069
|
+
}, this);
|
|
6070
|
+
window$1.addEventListener('pagehide', function(ev) {
|
|
6071
|
+
if (ev['persisted']) {
|
|
6072
|
+
flush_on_unload();
|
|
6073
|
+
}
|
|
6074
|
+
});
|
|
6075
|
+
window$1.addEventListener('visibilitychange', function() {
|
|
6076
|
+
if (document$1['visibilityState'] === 'hidden') {
|
|
6077
|
+
flush_on_unload();
|
|
6078
|
+
}
|
|
6079
|
+
});
|
|
6533
6080
|
}
|
|
6534
6081
|
}
|
|
6535
6082
|
}
|
|
6536
6083
|
|
|
6537
6084
|
this['persistence'] = this['cookie'] = new MixpanelPersistence(this['config']);
|
|
6085
|
+
this.unpersisted_superprops = {};
|
|
6538
6086
|
this._gdpr_init();
|
|
6539
6087
|
|
|
6540
6088
|
var uuid = _.UUID();
|
|
@@ -6584,7 +6132,7 @@
|
|
|
6584
6132
|
|
|
6585
6133
|
MixpanelLib.prototype._track_dom = function(DomClass, args) {
|
|
6586
6134
|
if (this.get_config('img')) {
|
|
6587
|
-
console
|
|
6135
|
+
console.error('You can\'t use DOM tracking functions with img = true.');
|
|
6588
6136
|
return false;
|
|
6589
6137
|
}
|
|
6590
6138
|
|
|
@@ -6694,9 +6242,16 @@
|
|
|
6694
6242
|
try {
|
|
6695
6243
|
succeeded = sendBeacon(url, body_data);
|
|
6696
6244
|
} catch (e) {
|
|
6697
|
-
console
|
|
6245
|
+
console.error(e);
|
|
6698
6246
|
succeeded = false;
|
|
6699
6247
|
}
|
|
6248
|
+
try {
|
|
6249
|
+
if (callback) {
|
|
6250
|
+
callback(succeeded ? 1 : 0);
|
|
6251
|
+
}
|
|
6252
|
+
} catch (e) {
|
|
6253
|
+
console.error(e);
|
|
6254
|
+
}
|
|
6700
6255
|
} else if (USE_XHR) {
|
|
6701
6256
|
try {
|
|
6702
6257
|
var req = new XMLHttpRequest();
|
|
@@ -6727,7 +6282,7 @@
|
|
|
6727
6282
|
try {
|
|
6728
6283
|
response = _.JSONDecode(req.responseText);
|
|
6729
6284
|
} catch (e) {
|
|
6730
|
-
console
|
|
6285
|
+
console.error(e);
|
|
6731
6286
|
if (options.ignore_json_errors) {
|
|
6732
6287
|
response = req.responseText;
|
|
6733
6288
|
} else {
|
|
@@ -6750,7 +6305,7 @@
|
|
|
6750
6305
|
} else {
|
|
6751
6306
|
error = 'Bad HTTP status: ' + req.status + ' ' + req.statusText;
|
|
6752
6307
|
}
|
|
6753
|
-
console
|
|
6308
|
+
console.error(error);
|
|
6754
6309
|
if (callback) {
|
|
6755
6310
|
if (verbose_mode) {
|
|
6756
6311
|
callback({status: 0, error: error, xhr_req: req});
|
|
@@ -6763,7 +6318,7 @@
|
|
|
6763
6318
|
};
|
|
6764
6319
|
req.send(body_data);
|
|
6765
6320
|
} catch (e) {
|
|
6766
|
-
console
|
|
6321
|
+
console.error(e);
|
|
6767
6322
|
succeeded = false;
|
|
6768
6323
|
}
|
|
6769
6324
|
} else {
|
|
@@ -6831,32 +6386,53 @@
|
|
|
6831
6386
|
|
|
6832
6387
|
// request queueing utils
|
|
6833
6388
|
|
|
6834
|
-
MixpanelLib.prototype.
|
|
6389
|
+
MixpanelLib.prototype.are_batchers_initialized = function() {
|
|
6390
|
+
return !!this.request_batchers.events;
|
|
6391
|
+
};
|
|
6392
|
+
|
|
6393
|
+
MixpanelLib.prototype.init_batchers = function() {
|
|
6835
6394
|
var token = this.get_config('token');
|
|
6836
|
-
if (!this.
|
|
6837
|
-
var
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
this
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6395
|
+
if (!this.are_batchers_initialized()) {
|
|
6396
|
+
var batcher_for = _.bind(function(attrs) {
|
|
6397
|
+
return new RequestBatcher(
|
|
6398
|
+
'__mpq_' + token + attrs.queue_suffix,
|
|
6399
|
+
{
|
|
6400
|
+
libConfig: this['config'],
|
|
6401
|
+
sendRequestFunc: _.bind(function(data, options, cb) {
|
|
6402
|
+
this._send_request(
|
|
6403
|
+
this.get_config('api_host') + attrs.endpoint,
|
|
6404
|
+
encode_data_for_request(data),
|
|
6405
|
+
options,
|
|
6406
|
+
this._prepare_callback(cb, data)
|
|
6407
|
+
);
|
|
6408
|
+
}, this),
|
|
6409
|
+
beforeSendHook: _.bind(function(item) {
|
|
6410
|
+
return this._run_hook('before_send_' + attrs.type, item);
|
|
6411
|
+
}, this)
|
|
6412
|
+
}
|
|
6413
|
+
);
|
|
6414
|
+
}, this);
|
|
6848
6415
|
this.request_batchers = {
|
|
6849
|
-
events:
|
|
6850
|
-
people:
|
|
6851
|
-
groups:
|
|
6416
|
+
events: batcher_for({type: 'events', endpoint: '/track/', queue_suffix: '_ev'}),
|
|
6417
|
+
people: batcher_for({type: 'people', endpoint: '/engage/', queue_suffix: '_pp'}),
|
|
6418
|
+
groups: batcher_for({type: 'groups', endpoint: '/groups/', queue_suffix: '_gr'})
|
|
6852
6419
|
};
|
|
6853
6420
|
}
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
}
|
|
6421
|
+
if (this.get_config('batch_autostart')) {
|
|
6422
|
+
this.start_batch_senders();
|
|
6423
|
+
}
|
|
6857
6424
|
};
|
|
6858
6425
|
|
|
6859
|
-
MixpanelLib.prototype.
|
|
6426
|
+
MixpanelLib.prototype.start_batch_senders = function() {
|
|
6427
|
+
if (this.are_batchers_initialized()) {
|
|
6428
|
+
this._batch_requests = true;
|
|
6429
|
+
_.each(this.request_batchers, function(batcher) {
|
|
6430
|
+
batcher.start();
|
|
6431
|
+
});
|
|
6432
|
+
}
|
|
6433
|
+
};
|
|
6434
|
+
|
|
6435
|
+
MixpanelLib.prototype.stop_batch_senders = function() {
|
|
6860
6436
|
this._batch_requests = false;
|
|
6861
6437
|
_.each(this.request_batchers, function(batcher) {
|
|
6862
6438
|
batcher.stop();
|
|
@@ -6901,23 +6477,30 @@
|
|
|
6901
6477
|
|
|
6902
6478
|
// internal method for handling track vs batch-enqueue logic
|
|
6903
6479
|
MixpanelLib.prototype._track_or_batch = function(options, callback) {
|
|
6904
|
-
var truncated_data = options.
|
|
6480
|
+
var truncated_data = _.truncate(options.data, 255);
|
|
6905
6481
|
var endpoint = options.endpoint;
|
|
6906
6482
|
var batcher = options.batcher;
|
|
6907
6483
|
var should_send_immediately = options.should_send_immediately;
|
|
6908
6484
|
var send_request_options = options.send_request_options || {};
|
|
6909
|
-
callback = callback ||
|
|
6485
|
+
callback = callback || NOOP_FUNC;
|
|
6910
6486
|
|
|
6911
6487
|
var request_enqueued_or_initiated = true;
|
|
6912
6488
|
var send_request_immediately = _.bind(function() {
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
this.
|
|
6920
|
-
|
|
6489
|
+
if (!send_request_options.skip_hooks) {
|
|
6490
|
+
truncated_data = this._run_hook('before_send_' + options.type, truncated_data);
|
|
6491
|
+
}
|
|
6492
|
+
if (truncated_data) {
|
|
6493
|
+
console.log('MIXPANEL REQUEST:');
|
|
6494
|
+
console.log(truncated_data);
|
|
6495
|
+
return this._send_request(
|
|
6496
|
+
endpoint,
|
|
6497
|
+
encode_data_for_request(truncated_data),
|
|
6498
|
+
send_request_options,
|
|
6499
|
+
this._prepare_callback(callback, truncated_data)
|
|
6500
|
+
);
|
|
6501
|
+
} else {
|
|
6502
|
+
return null;
|
|
6503
|
+
}
|
|
6921
6504
|
}, this);
|
|
6922
6505
|
|
|
6923
6506
|
if (this._batch_requests && !should_send_immediately) {
|
|
@@ -6970,11 +6553,11 @@
|
|
|
6970
6553
|
}
|
|
6971
6554
|
var should_send_immediately = options['send_immediately'];
|
|
6972
6555
|
if (typeof callback !== 'function') {
|
|
6973
|
-
callback =
|
|
6556
|
+
callback = NOOP_FUNC;
|
|
6974
6557
|
}
|
|
6975
6558
|
|
|
6976
6559
|
if (_.isUndefined(event_name)) {
|
|
6977
|
-
console
|
|
6560
|
+
console.error('No event name provided to mixpanel.track');
|
|
6978
6561
|
return;
|
|
6979
6562
|
}
|
|
6980
6563
|
|
|
@@ -7005,6 +6588,7 @@
|
|
|
7005
6588
|
{},
|
|
7006
6589
|
_.info.properties(),
|
|
7007
6590
|
this['persistence'].properties(),
|
|
6591
|
+
this.unpersisted_superprops,
|
|
7008
6592
|
properties
|
|
7009
6593
|
);
|
|
7010
6594
|
|
|
@@ -7014,7 +6598,7 @@
|
|
|
7014
6598
|
delete properties[blacklisted_prop];
|
|
7015
6599
|
});
|
|
7016
6600
|
} else {
|
|
7017
|
-
console
|
|
6601
|
+
console.error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
7018
6602
|
}
|
|
7019
6603
|
|
|
7020
6604
|
var data = {
|
|
@@ -7022,7 +6606,8 @@
|
|
|
7022
6606
|
'properties': properties
|
|
7023
6607
|
};
|
|
7024
6608
|
var ret = this._track_or_batch({
|
|
7025
|
-
|
|
6609
|
+
type: 'events',
|
|
6610
|
+
data: data,
|
|
7026
6611
|
endpoint: this.get_config('api_host') + '/track/',
|
|
7027
6612
|
batcher: this.request_batchers.events,
|
|
7028
6613
|
should_send_immediately: should_send_immediately,
|
|
@@ -7258,7 +6843,7 @@
|
|
|
7258
6843
|
*/
|
|
7259
6844
|
MixpanelLib.prototype.time_event = function(event_name) {
|
|
7260
6845
|
if (_.isUndefined(event_name)) {
|
|
7261
|
-
console
|
|
6846
|
+
console.error('No event name provided to mixpanel.time_event');
|
|
7262
6847
|
return;
|
|
7263
6848
|
}
|
|
7264
6849
|
|
|
@@ -7269,6 +6854,27 @@
|
|
|
7269
6854
|
this['persistence'].set_event_timer(event_name, new Date().getTime());
|
|
7270
6855
|
};
|
|
7271
6856
|
|
|
6857
|
+
var REGISTER_DEFAULTS = {
|
|
6858
|
+
'persistent': true
|
|
6859
|
+
};
|
|
6860
|
+
/**
|
|
6861
|
+
* Helper to parse options param for register methods, maintaining
|
|
6862
|
+
* legacy support for plain "days" param instead of options object
|
|
6863
|
+
* @param {Number|Object} [days_or_options] 'days' option (Number), or Options object for register methods
|
|
6864
|
+
* @returns {Object} options object
|
|
6865
|
+
*/
|
|
6866
|
+
var options_for_register = function(days_or_options) {
|
|
6867
|
+
var options;
|
|
6868
|
+
if (_.isObject(days_or_options)) {
|
|
6869
|
+
options = days_or_options;
|
|
6870
|
+
} else if (!_.isUndefined(days_or_options)) {
|
|
6871
|
+
options = {'days': days_or_options};
|
|
6872
|
+
} else {
|
|
6873
|
+
options = {};
|
|
6874
|
+
}
|
|
6875
|
+
return _.extend({}, REGISTER_DEFAULTS, options);
|
|
6876
|
+
};
|
|
6877
|
+
|
|
7272
6878
|
/**
|
|
7273
6879
|
* Register a set of super properties, which are included with all
|
|
7274
6880
|
* events. This will overwrite previous super property values.
|
|
@@ -7284,11 +6890,21 @@
|
|
|
7284
6890
|
* 'Account Type': 'Free'
|
|
7285
6891
|
* });
|
|
7286
6892
|
*
|
|
6893
|
+
* // register only for the current pageload
|
|
6894
|
+
* mixpanel.register({'Name': 'Pat'}, {persistent: false});
|
|
6895
|
+
*
|
|
7287
6896
|
* @param {Object} properties An associative array of properties to store about the user
|
|
7288
|
-
* @param {Number} [
|
|
6897
|
+
* @param {Number|Object} [days_or_options] Options object or number of days since the user's last visit to store the super properties (only valid for persisted props)
|
|
6898
|
+
* @param {boolean} [days_or_options.days] - number of days since the user's last visit to store the super properties (only valid for persisted props)
|
|
6899
|
+
* @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
|
|
7289
6900
|
*/
|
|
7290
|
-
MixpanelLib.prototype.register = function(props,
|
|
7291
|
-
|
|
6901
|
+
MixpanelLib.prototype.register = function(props, days_or_options) {
|
|
6902
|
+
var options = options_for_register(days_or_options);
|
|
6903
|
+
if (options['persistent']) {
|
|
6904
|
+
this['persistence'].register(props, options['days']);
|
|
6905
|
+
} else {
|
|
6906
|
+
_.extend(this.unpersisted_superprops, props);
|
|
6907
|
+
}
|
|
7292
6908
|
};
|
|
7293
6909
|
|
|
7294
6910
|
/**
|
|
@@ -7302,6 +6918,11 @@
|
|
|
7302
6918
|
* 'First Login Date': new Date().toISOString()
|
|
7303
6919
|
* });
|
|
7304
6920
|
*
|
|
6921
|
+
* // register once, only for the current pageload
|
|
6922
|
+
* mixpanel.register_once({
|
|
6923
|
+
* 'First interaction time': new Date().toISOString()
|
|
6924
|
+
* }, 'None', {persistent: false});
|
|
6925
|
+
*
|
|
7305
6926
|
* ### Notes:
|
|
7306
6927
|
*
|
|
7307
6928
|
* If default_value is specified, current super properties
|
|
@@ -7309,19 +6930,40 @@
|
|
|
7309
6930
|
*
|
|
7310
6931
|
* @param {Object} properties An associative array of properties to store about the user
|
|
7311
6932
|
* @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None'
|
|
7312
|
-
* @param {Number} [
|
|
6933
|
+
* @param {Number|Object} [days_or_options] Options object or number of days since the user's last visit to store the super properties (only valid for persisted props)
|
|
6934
|
+
* @param {boolean} [days_or_options.days] - number of days since the user's last visit to store the super properties (only valid for persisted props)
|
|
6935
|
+
* @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
|
|
7313
6936
|
*/
|
|
7314
|
-
MixpanelLib.prototype.register_once = function(props, default_value,
|
|
7315
|
-
|
|
6937
|
+
MixpanelLib.prototype.register_once = function(props, default_value, days_or_options) {
|
|
6938
|
+
var options = options_for_register(days_or_options);
|
|
6939
|
+
if (options['persistent']) {
|
|
6940
|
+
this['persistence'].register_once(props, default_value, options['days']);
|
|
6941
|
+
} else {
|
|
6942
|
+
if (typeof(default_value) === 'undefined') {
|
|
6943
|
+
default_value = 'None';
|
|
6944
|
+
}
|
|
6945
|
+
_.each(props, function(val, prop) {
|
|
6946
|
+
if (!this.unpersisted_superprops.hasOwnProperty(prop) || this.unpersisted_superprops[prop] === default_value) {
|
|
6947
|
+
this.unpersisted_superprops[prop] = val;
|
|
6948
|
+
}
|
|
6949
|
+
}, this);
|
|
6950
|
+
}
|
|
7316
6951
|
};
|
|
7317
6952
|
|
|
7318
6953
|
/**
|
|
7319
6954
|
* Delete a super property stored with the current user.
|
|
7320
6955
|
*
|
|
7321
6956
|
* @param {String} property The name of the super property to remove
|
|
6957
|
+
* @param {Object} [options]
|
|
6958
|
+
* @param {boolean} [options.persistent=true] - whether to look in persistent storage (cookie/localStorage)
|
|
7322
6959
|
*/
|
|
7323
|
-
MixpanelLib.prototype.unregister = function(property) {
|
|
7324
|
-
|
|
6960
|
+
MixpanelLib.prototype.unregister = function(property, options) {
|
|
6961
|
+
options = options_for_register(options);
|
|
6962
|
+
if (options['persistent']) {
|
|
6963
|
+
this['persistence'].unregister(property);
|
|
6964
|
+
} else {
|
|
6965
|
+
delete this.unpersisted_superprops[property];
|
|
6966
|
+
}
|
|
7325
6967
|
};
|
|
7326
6968
|
|
|
7327
6969
|
MixpanelLib.prototype._register_single = function(prop, value) {
|
|
@@ -7392,7 +7034,10 @@
|
|
|
7392
7034
|
// send an $identify event any time the distinct_id is changing - logic on the server
|
|
7393
7035
|
// will determine whether or not to do anything with it.
|
|
7394
7036
|
if (new_distinct_id !== previous_distinct_id) {
|
|
7395
|
-
this.track('$identify', {
|
|
7037
|
+
this.track('$identify', {
|
|
7038
|
+
'distinct_id': new_distinct_id,
|
|
7039
|
+
'$anon_distinct_id': previous_distinct_id
|
|
7040
|
+
}, {skip_hooks: true});
|
|
7396
7041
|
}
|
|
7397
7042
|
};
|
|
7398
7043
|
|
|
@@ -7471,7 +7116,7 @@
|
|
|
7471
7116
|
// mixpanel.people.identify() call made for this user. It is VERY BAD to make an alias with
|
|
7472
7117
|
// this ID, as it will duplicate users.
|
|
7473
7118
|
if (alias === this.get_property(PEOPLE_DISTINCT_ID_KEY)) {
|
|
7474
|
-
console
|
|
7119
|
+
console.critical('Attempting to create alias for existing People user - aborting.');
|
|
7475
7120
|
return -2;
|
|
7476
7121
|
}
|
|
7477
7122
|
|
|
@@ -7481,12 +7126,17 @@
|
|
|
7481
7126
|
}
|
|
7482
7127
|
if (alias !== original) {
|
|
7483
7128
|
this._register_single(ALIAS_ID_KEY, alias);
|
|
7484
|
-
return this.track('$create_alias', {
|
|
7129
|
+
return this.track('$create_alias', {
|
|
7130
|
+
'alias': alias,
|
|
7131
|
+
'distinct_id': original
|
|
7132
|
+
}, {
|
|
7133
|
+
skip_hooks: true
|
|
7134
|
+
}, function() {
|
|
7485
7135
|
// Flush the people queue
|
|
7486
7136
|
_this.identify(alias);
|
|
7487
7137
|
});
|
|
7488
7138
|
} else {
|
|
7489
|
-
console
|
|
7139
|
+
console.error('alias matches current distinct_id - skipping api call.');
|
|
7490
7140
|
this.identify(alias);
|
|
7491
7141
|
return -1;
|
|
7492
7142
|
}
|
|
@@ -7660,6 +7310,21 @@
|
|
|
7660
7310
|
return this['config'][prop_name];
|
|
7661
7311
|
};
|
|
7662
7312
|
|
|
7313
|
+
/**
|
|
7314
|
+
* Fetch a hook function from config, with safe default, and run it
|
|
7315
|
+
* against the given arguments
|
|
7316
|
+
* @param {string} hook_name which hook to retrieve
|
|
7317
|
+
* @returns {any|null} return value of user-provided hook, or null if nothing was returned
|
|
7318
|
+
*/
|
|
7319
|
+
MixpanelLib.prototype._run_hook = function(hook_name) {
|
|
7320
|
+
var ret = (this['config']['hooks'][hook_name] || IDENTITY_FUNC).apply(this, slice.call(arguments, 1));
|
|
7321
|
+
if (typeof ret === 'undefined') {
|
|
7322
|
+
console.error(hook_name + ' hook did not return a value');
|
|
7323
|
+
ret = null;
|
|
7324
|
+
}
|
|
7325
|
+
return ret;
|
|
7326
|
+
};
|
|
7327
|
+
|
|
7663
7328
|
/**
|
|
7664
7329
|
* Returns the value of the super property named property_name. If no such
|
|
7665
7330
|
* property is set, get_property() will return the undefined value.
|
|
@@ -7720,7 +7385,7 @@
|
|
|
7720
7385
|
return;
|
|
7721
7386
|
}
|
|
7722
7387
|
|
|
7723
|
-
console
|
|
7388
|
+
console.log('MIXPANEL NOTIFICATION CHECK');
|
|
7724
7389
|
|
|
7725
7390
|
var data = {
|
|
7726
7391
|
'verbose': true,
|
|
@@ -8045,7 +7710,8 @@
|
|
|
8045
7710
|
MixpanelLib.prototype['add_group'] = MixpanelLib.prototype.add_group;
|
|
8046
7711
|
MixpanelLib.prototype['remove_group'] = MixpanelLib.prototype.remove_group;
|
|
8047
7712
|
MixpanelLib.prototype['track_with_groups'] = MixpanelLib.prototype.track_with_groups;
|
|
8048
|
-
MixpanelLib.prototype['
|
|
7713
|
+
MixpanelLib.prototype['start_batch_senders'] = MixpanelLib.prototype.start_batch_senders;
|
|
7714
|
+
MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.prototype.stop_batch_senders;
|
|
8049
7715
|
|
|
8050
7716
|
// MixpanelPersistence Exports
|
|
8051
7717
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|