mixpanel-browser 2.55.0 → 2.56.0-ac-alpha-3
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/.eslintrc.json +31 -1
- package/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/dist/mixpanel-core.cjs.js +727 -69
- package/dist/mixpanel-recorder.js +215 -88
- package/dist/mixpanel-recorder.min.js +10 -9
- package/dist/mixpanel-recorder.min.js.map +1 -0
- package/dist/mixpanel-with-async-recorder.cjs.js +727 -69
- package/dist/mixpanel.amd.js +913 -147
- package/dist/mixpanel.cjs.js +913 -147
- package/dist/mixpanel.globals.js +727 -69
- package/dist/mixpanel.min.js +124 -110
- package/dist/mixpanel.min.js.map +8 -0
- package/dist/mixpanel.module.js +913 -147
- package/dist/mixpanel.umd.js +913 -147
- package/package.json +1 -1
- package/src/autocapture/index.js +250 -0
- package/src/autocapture/utils.js +413 -0
- package/src/config.js +1 -1
- package/src/mixpanel-core.js +31 -59
- package/src/recorder/index.js +39 -232
- package/src/recorder/rollup.config.js +2 -1
- package/src/recorder/session-recording.js +305 -0
- package/src/request-batcher.js +7 -2
- package/src/request-queue.js +5 -3
- package/src/utils.js +55 -14
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var Config = {
|
|
4
4
|
DEBUG: false,
|
|
5
|
-
LIB_VERSION: '2.
|
|
5
|
+
LIB_VERSION: '2.56.0-ac-alpha-3'
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
@@ -14,13 +14,16 @@ if (typeof(window) === 'undefined') {
|
|
|
14
14
|
hostname: ''
|
|
15
15
|
};
|
|
16
16
|
win = {
|
|
17
|
-
navigator: { userAgent: '' },
|
|
17
|
+
navigator: { userAgent: '', onLine: true },
|
|
18
18
|
document: {
|
|
19
|
+
createElement: function() { return {}; },
|
|
19
20
|
location: loc,
|
|
20
21
|
referrer: ''
|
|
21
22
|
},
|
|
22
23
|
screen: { width: 0, height: 0 },
|
|
23
|
-
location: loc
|
|
24
|
+
location: loc,
|
|
25
|
+
addEventListener: function() {},
|
|
26
|
+
removeEventListener: function() {}
|
|
24
27
|
};
|
|
25
28
|
} else {
|
|
26
29
|
win = window;
|
|
@@ -131,6 +134,29 @@ var console_with_prefix = function(prefix) {
|
|
|
131
134
|
};
|
|
132
135
|
|
|
133
136
|
|
|
137
|
+
var safewrap = function(f) {
|
|
138
|
+
return function() {
|
|
139
|
+
try {
|
|
140
|
+
return f.apply(this, arguments);
|
|
141
|
+
} catch (e) {
|
|
142
|
+
console.critical('Implementation error. Please turn on debug and contact support@mixpanel.com.');
|
|
143
|
+
if (Config.DEBUG){
|
|
144
|
+
console.critical(e);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
var safewrapClass = function(klass) {
|
|
151
|
+
var proto = klass.prototype;
|
|
152
|
+
for (var func in proto) {
|
|
153
|
+
if (typeof(proto[func]) === 'function') {
|
|
154
|
+
proto[func] = safewrap(proto[func]);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
|
|
134
160
|
// UNDERSCORE
|
|
135
161
|
// Embed part of the Underscore Library
|
|
136
162
|
_.bind = function(func, context) {
|
|
@@ -968,7 +994,7 @@ _.HTTPBuildQuery = function(formdata, arg_separator) {
|
|
|
968
994
|
_.getQueryParam = function(url, param) {
|
|
969
995
|
// Expects a raw URL
|
|
970
996
|
|
|
971
|
-
param = param.replace(/[[]
|
|
997
|
+
param = param.replace(/[[]/g, '\\[').replace(/[\]]/g, '\\]');
|
|
972
998
|
var regexS = '[\\?&]' + param + '=([^&#]*)',
|
|
973
999
|
regex = new RegExp(regexS),
|
|
974
1000
|
results = regex.exec(url);
|
|
@@ -1425,8 +1451,8 @@ _.dom_query = (function() {
|
|
|
1425
1451
|
};
|
|
1426
1452
|
})();
|
|
1427
1453
|
|
|
1428
|
-
var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
|
|
1429
|
-
var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'ttclid', 'twclid', 'wbraid'];
|
|
1454
|
+
var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'utm_id', 'utm_source_platform','utm_campaign_id', 'utm_creative_format', 'utm_marketing_tactic'];
|
|
1455
|
+
var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'sccid', 'ttclid', 'twclid', 'wbraid'];
|
|
1430
1456
|
|
|
1431
1457
|
_.info = {
|
|
1432
1458
|
campaignParams: function(default_value) {
|
|
@@ -1708,6 +1734,15 @@ var extract_domain = function(hostname) {
|
|
|
1708
1734
|
return matches ? matches[0] : '';
|
|
1709
1735
|
};
|
|
1710
1736
|
|
|
1737
|
+
/**
|
|
1738
|
+
* Check whether we have network connection. default to true for browsers that don't support navigator.onLine (IE)
|
|
1739
|
+
* @returns {boolean}
|
|
1740
|
+
*/
|
|
1741
|
+
var isOnline = function() {
|
|
1742
|
+
var onLine = win.navigator['onLine'];
|
|
1743
|
+
return _.isUndefined(onLine) || onLine;
|
|
1744
|
+
};
|
|
1745
|
+
|
|
1711
1746
|
var JSONStringify = null, JSONParse = null;
|
|
1712
1747
|
if (typeof JSON !== 'undefined') {
|
|
1713
1748
|
JSONStringify = JSON.stringify;
|
|
@@ -1729,6 +1764,651 @@ _['info']['browser'] = _.info.browser;
|
|
|
1729
1764
|
_['info']['browserVersion'] = _.info.browserVersion;
|
|
1730
1765
|
_['info']['properties'] = _.info.properties;
|
|
1731
1766
|
|
|
1767
|
+
// stateless utils
|
|
1768
|
+
|
|
1769
|
+
var EV_CHANGE = 'change';
|
|
1770
|
+
var EV_CLICK = 'click';
|
|
1771
|
+
var EV_HASHCHANGE = 'hashchange';
|
|
1772
|
+
var EV_MP_LOCATION_CHANGE = 'mp_locationchange';
|
|
1773
|
+
var EV_POPSTATE = 'popstate';
|
|
1774
|
+
var EV_SCROLL = 'scrollend';
|
|
1775
|
+
var EV_SUBMIT = 'submit';
|
|
1776
|
+
|
|
1777
|
+
var CLICK_EVENT_PROPS = [
|
|
1778
|
+
'clientX', 'clientY',
|
|
1779
|
+
'offsetX', 'offsetY',
|
|
1780
|
+
'pageX', 'pageY',
|
|
1781
|
+
'screenX', 'screenY',
|
|
1782
|
+
'x', 'y'
|
|
1783
|
+
];
|
|
1784
|
+
var OPT_IN_CLASSES = ['mp-include'];
|
|
1785
|
+
var OPT_OUT_CLASSES = ['mp-no-track'];
|
|
1786
|
+
var SENSITIVE_DATA_CLASSES = OPT_OUT_CLASSES.concat(['mp-sensitive']);
|
|
1787
|
+
var TRACKED_ATTRS = [
|
|
1788
|
+
'aria-label', 'aria-labelledby', 'aria-describedby',
|
|
1789
|
+
'href', 'name', 'role', 'title', 'type'
|
|
1790
|
+
];
|
|
1791
|
+
|
|
1792
|
+
var logger$3 = console_with_prefix('autocapture');
|
|
1793
|
+
|
|
1794
|
+
|
|
1795
|
+
function getClasses(el) {
|
|
1796
|
+
var classes = {};
|
|
1797
|
+
var classList = getClassName(el).split(' ');
|
|
1798
|
+
for (var i = 0; i < classList.length; i++) {
|
|
1799
|
+
var cls = classList[i];
|
|
1800
|
+
if (cls) {
|
|
1801
|
+
classes[cls] = true;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
return classes;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
/*
|
|
1808
|
+
* Get the className of an element, accounting for edge cases where element.className is an object
|
|
1809
|
+
* @param {Element} el - element to get the className of
|
|
1810
|
+
* @returns {string} the element's class
|
|
1811
|
+
*/
|
|
1812
|
+
function getClassName(el) {
|
|
1813
|
+
switch(typeof el.className) {
|
|
1814
|
+
case 'string':
|
|
1815
|
+
return el.className;
|
|
1816
|
+
case 'object': // handle cases where className might be SVGAnimatedString or some other type
|
|
1817
|
+
return el.className.baseVal || el.getAttribute('class') || '';
|
|
1818
|
+
default: // future proof
|
|
1819
|
+
return '';
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
function getPreviousElementSibling(el) {
|
|
1824
|
+
if (el.previousElementSibling) {
|
|
1825
|
+
return el.previousElementSibling;
|
|
1826
|
+
} else {
|
|
1827
|
+
do {
|
|
1828
|
+
el = el.previousSibling;
|
|
1829
|
+
} while (el && !isElementNode(el));
|
|
1830
|
+
return el;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
function getPropertiesFromElement(el) {
|
|
1835
|
+
var props = {
|
|
1836
|
+
'$classes': getClassName(el).split(' '),
|
|
1837
|
+
'$tag_name': el.tagName.toLowerCase()
|
|
1838
|
+
};
|
|
1839
|
+
var elId = el.id;
|
|
1840
|
+
if (elId) {
|
|
1841
|
+
props['$id'] = elId;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
if (shouldTrackElement(el)) {
|
|
1845
|
+
_.each(TRACKED_ATTRS, function(attr) {
|
|
1846
|
+
if (el.hasAttribute(attr)) {
|
|
1847
|
+
var attrVal = el.getAttribute(attr);
|
|
1848
|
+
if (shouldTrackValue(attrVal)) {
|
|
1849
|
+
props['$attr-' + attr] = attrVal;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
var nthChild = 1;
|
|
1856
|
+
var nthOfType = 1;
|
|
1857
|
+
var currentElem = el;
|
|
1858
|
+
while (currentElem = getPreviousElementSibling(currentElem)) { // eslint-disable-line no-cond-assign
|
|
1859
|
+
nthChild++;
|
|
1860
|
+
if (currentElem.tagName === el.tagName) {
|
|
1861
|
+
nthOfType++;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
props['$nth_child'] = nthChild;
|
|
1865
|
+
props['$nth_of_type'] = nthOfType;
|
|
1866
|
+
|
|
1867
|
+
return props;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
function getPropsForDOMEvent(ev, blockSelectors, captureTextContent) {
|
|
1871
|
+
blockSelectors = blockSelectors || [];
|
|
1872
|
+
var props = null;
|
|
1873
|
+
|
|
1874
|
+
var target = typeof ev.target === 'undefined' ? ev.srcElement : ev.target;
|
|
1875
|
+
if (isTextNode(target)) { // defeat Safari bug (see: http://www.quirksmode.org/js/events_properties.html)
|
|
1876
|
+
target = target.parentNode;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
if (shouldTrackDomEvent(target, ev)) {
|
|
1880
|
+
var targetElementList = [target];
|
|
1881
|
+
var curEl = target;
|
|
1882
|
+
while (curEl.parentNode && !isTag(curEl, 'body')) {
|
|
1883
|
+
targetElementList.push(curEl.parentNode);
|
|
1884
|
+
curEl = curEl.parentNode;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
var elementsJson = [];
|
|
1888
|
+
var href, explicitNoTrack = false;
|
|
1889
|
+
_.each(targetElementList, function(el) {
|
|
1890
|
+
var shouldTrackEl = shouldTrackElement(el);
|
|
1891
|
+
|
|
1892
|
+
// if the element or a parent element is an anchor tag
|
|
1893
|
+
// include the href as a property
|
|
1894
|
+
if (el.tagName.toLowerCase() === 'a') {
|
|
1895
|
+
href = el.getAttribute('href');
|
|
1896
|
+
href = shouldTrackEl && shouldTrackValue(href) && href;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
// allow users to programmatically prevent tracking of elements by adding classes such as 'mp-no-track'
|
|
1900
|
+
var classes = getClasses(el);
|
|
1901
|
+
_.each(OPT_OUT_CLASSES, function(cls) {
|
|
1902
|
+
if (classes[cls]) {
|
|
1903
|
+
explicitNoTrack = true;
|
|
1904
|
+
}
|
|
1905
|
+
});
|
|
1906
|
+
|
|
1907
|
+
if (!explicitNoTrack) {
|
|
1908
|
+
// programmatically prevent tracking of elements that match CSS selectors
|
|
1909
|
+
_.each(blockSelectors, function(sel) {
|
|
1910
|
+
try {
|
|
1911
|
+
if (el.matches(sel)) {
|
|
1912
|
+
explicitNoTrack = true;
|
|
1913
|
+
}
|
|
1914
|
+
} catch (err) {
|
|
1915
|
+
logger$3.critical('Error while checking selector: ' + sel, err);
|
|
1916
|
+
}
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
elementsJson.push(getPropertiesFromElement(el));
|
|
1921
|
+
}, this);
|
|
1922
|
+
|
|
1923
|
+
if (!explicitNoTrack) {
|
|
1924
|
+
props = {
|
|
1925
|
+
'$event_type': ev.type,
|
|
1926
|
+
'$host': win.location.host,
|
|
1927
|
+
'$pathname': win.location.pathname,
|
|
1928
|
+
'$elements': elementsJson,
|
|
1929
|
+
'$el_attr__href': href
|
|
1930
|
+
};
|
|
1931
|
+
|
|
1932
|
+
if (captureTextContent) {
|
|
1933
|
+
var elementText = getSafeText(target);
|
|
1934
|
+
if (elementText && elementText.length) {
|
|
1935
|
+
props['$el_text'] = elementText;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
if (ev.type === EV_CLICK) {
|
|
1940
|
+
_.each(CLICK_EVENT_PROPS, function(prop) {
|
|
1941
|
+
if (prop in ev) {
|
|
1942
|
+
props['$' + prop] = ev[prop];
|
|
1943
|
+
}
|
|
1944
|
+
});
|
|
1945
|
+
target = guessRealClickTarget(ev);
|
|
1946
|
+
}
|
|
1947
|
+
if (target) {
|
|
1948
|
+
props['$target'] = getPropertiesFromElement(target);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
return props;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
|
|
1957
|
+
/*
|
|
1958
|
+
* Get the direct text content of an element, protecting against sensitive data collection.
|
|
1959
|
+
* Concats textContent of each of the element's text node children; this avoids potential
|
|
1960
|
+
* collection of sensitive data that could happen if we used element.textContent and the
|
|
1961
|
+
* element had sensitive child elements, since element.textContent includes child content.
|
|
1962
|
+
* Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
|
|
1963
|
+
* @param {Element} el - element to get the text of
|
|
1964
|
+
* @returns {string} the element's direct text content
|
|
1965
|
+
*/
|
|
1966
|
+
function getSafeText(el) {
|
|
1967
|
+
var elText = '';
|
|
1968
|
+
|
|
1969
|
+
if (shouldTrackElement(el) && el.childNodes && el.childNodes.length) {
|
|
1970
|
+
_.each(el.childNodes, function(child) {
|
|
1971
|
+
if (isTextNode(child) && child.textContent) {
|
|
1972
|
+
elText += _.trim(child.textContent)
|
|
1973
|
+
// scrub potentially sensitive values
|
|
1974
|
+
.split(/(\s+)/).filter(shouldTrackValue).join('')
|
|
1975
|
+
// normalize whitespace
|
|
1976
|
+
.replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ')
|
|
1977
|
+
// truncate
|
|
1978
|
+
.substring(0, 255);
|
|
1979
|
+
}
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
return _.trim(elText);
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
function guessRealClickTarget(ev) {
|
|
1987
|
+
var target = ev.target;
|
|
1988
|
+
var composedPath = ev.composedPath();
|
|
1989
|
+
for (var i = 0; i < composedPath.length; i++) {
|
|
1990
|
+
var node = composedPath[i];
|
|
1991
|
+
var tagName = node.tagName && node.tagName.toLowerCase();
|
|
1992
|
+
if (
|
|
1993
|
+
tagName === 'a' ||
|
|
1994
|
+
tagName === 'button' ||
|
|
1995
|
+
tagName === 'input' ||
|
|
1996
|
+
tagName === 'select' ||
|
|
1997
|
+
(node.getAttribute && node.getAttribute('role') === 'button')
|
|
1998
|
+
) {
|
|
1999
|
+
target = node;
|
|
2000
|
+
break;
|
|
2001
|
+
}
|
|
2002
|
+
if (node === target) {
|
|
2003
|
+
break;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
return target;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
/*
|
|
2010
|
+
* Check whether an element has nodeType Node.ELEMENT_NODE
|
|
2011
|
+
* @param {Element} el - element to check
|
|
2012
|
+
* @returns {boolean} whether el is of the correct nodeType
|
|
2013
|
+
*/
|
|
2014
|
+
function isElementNode(el) {
|
|
2015
|
+
return el && el.nodeType === 1; // Node.ELEMENT_NODE - use integer constant for browser portability
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
/*
|
|
2019
|
+
* Check whether an element is of a given tag type.
|
|
2020
|
+
* Due to potential reference discrepancies (such as the webcomponents.js polyfill),
|
|
2021
|
+
* we want to match tagNames instead of specific references because something like
|
|
2022
|
+
* element === document.body won't always work because element might not be a native
|
|
2023
|
+
* element.
|
|
2024
|
+
* @param {Element} el - element to check
|
|
2025
|
+
* @param {string} tag - tag name (e.g., "div")
|
|
2026
|
+
* @returns {boolean} whether el is of the given tag type
|
|
2027
|
+
*/
|
|
2028
|
+
function isTag(el, tag) {
|
|
2029
|
+
return el && el.tagName && el.tagName.toLowerCase() === tag.toLowerCase();
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
/*
|
|
2033
|
+
* Check whether an element has nodeType Node.TEXT_NODE
|
|
2034
|
+
* @param {Element} el - element to check
|
|
2035
|
+
* @returns {boolean} whether el is of the correct nodeType
|
|
2036
|
+
*/
|
|
2037
|
+
function isTextNode(el) {
|
|
2038
|
+
return el && el.nodeType === 3; // Node.TEXT_NODE - use integer constant for browser portability
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
function minDOMApisSupported() {
|
|
2042
|
+
try {
|
|
2043
|
+
var testEl = document$1.createElement('div');
|
|
2044
|
+
return !!testEl['matches'];
|
|
2045
|
+
} catch (err) {
|
|
2046
|
+
return false;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
/*
|
|
2051
|
+
* Check whether a DOM event should be "tracked" or if it may contain sentitive data
|
|
2052
|
+
* using a variety of heuristics.
|
|
2053
|
+
* @param {Element} el - element to check
|
|
2054
|
+
* @param {Event} ev - event to check
|
|
2055
|
+
* @returns {boolean} whether the event should be tracked
|
|
2056
|
+
*/
|
|
2057
|
+
function shouldTrackDomEvent(el, ev) {
|
|
2058
|
+
if (!el || isTag(el, 'html') || !isElementNode(el)) {
|
|
2059
|
+
return false;
|
|
2060
|
+
}
|
|
2061
|
+
var tag = el.tagName.toLowerCase();
|
|
2062
|
+
switch (tag) {
|
|
2063
|
+
case 'form':
|
|
2064
|
+
return ev.type === EV_SUBMIT;
|
|
2065
|
+
case 'input':
|
|
2066
|
+
if (['button', 'submit'].indexOf(el.getAttribute('type')) === -1) {
|
|
2067
|
+
return ev.type === EV_CHANGE;
|
|
2068
|
+
} else {
|
|
2069
|
+
return ev.type === EV_CLICK;
|
|
2070
|
+
}
|
|
2071
|
+
case 'select':
|
|
2072
|
+
case 'textarea':
|
|
2073
|
+
return ev.type === EV_CHANGE;
|
|
2074
|
+
default:
|
|
2075
|
+
return ev.type === EV_CLICK;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
/*
|
|
2080
|
+
* Check whether a DOM element should be "tracked" or if it may contain sentitive data
|
|
2081
|
+
* using a variety of heuristics.
|
|
2082
|
+
* @param {Element} el - element to check
|
|
2083
|
+
* @returns {boolean} whether the element should be tracked
|
|
2084
|
+
*/
|
|
2085
|
+
function shouldTrackElement(el) {
|
|
2086
|
+
var i;
|
|
2087
|
+
|
|
2088
|
+
for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
|
|
2089
|
+
var classes = getClasses(curEl);
|
|
2090
|
+
for (i = 0; i < SENSITIVE_DATA_CLASSES.length; i++) {
|
|
2091
|
+
if (classes[SENSITIVE_DATA_CLASSES[i]]) {
|
|
2092
|
+
return false;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
var elClasses = getClasses(el);
|
|
2098
|
+
for (i = 0; i < OPT_IN_CLASSES.length; i++) {
|
|
2099
|
+
if (elClasses[OPT_IN_CLASSES[i]]) {
|
|
2100
|
+
return true;
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
// don't send data from inputs or similar elements since there will always be
|
|
2105
|
+
// a risk of clientside javascript placing sensitive data in attributes
|
|
2106
|
+
if (
|
|
2107
|
+
isTag(el, 'input') ||
|
|
2108
|
+
isTag(el, 'select') ||
|
|
2109
|
+
isTag(el, 'textarea') ||
|
|
2110
|
+
el.getAttribute('contenteditable') === 'true'
|
|
2111
|
+
) {
|
|
2112
|
+
return false;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
// don't include hidden or password fields
|
|
2116
|
+
var type = el.type || '';
|
|
2117
|
+
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"]
|
|
2118
|
+
switch(type.toLowerCase()) {
|
|
2119
|
+
case 'hidden':
|
|
2120
|
+
return false;
|
|
2121
|
+
case 'password':
|
|
2122
|
+
return false;
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// filter out data from fields that look like sensitive fields
|
|
2127
|
+
var name = el.name || el.id || '';
|
|
2128
|
+
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"]
|
|
2129
|
+
var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
|
|
2130
|
+
if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
|
|
2131
|
+
return false;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
return true;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
|
|
2139
|
+
/*
|
|
2140
|
+
* Check whether a string value should be "tracked" or if it may contain sentitive data
|
|
2141
|
+
* using a variety of heuristics.
|
|
2142
|
+
* @param {string} value - string value to check
|
|
2143
|
+
* @returns {boolean} whether the element should be tracked
|
|
2144
|
+
*/
|
|
2145
|
+
function shouldTrackValue(value) {
|
|
2146
|
+
if (value === null || _.isUndefined(value)) {
|
|
2147
|
+
return false;
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
if (typeof value === 'string') {
|
|
2151
|
+
value = _.trim(value);
|
|
2152
|
+
|
|
2153
|
+
// check to see if input value looks like a credit card number
|
|
2154
|
+
// see: https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s20.html
|
|
2155
|
+
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}))$/;
|
|
2156
|
+
if (ccRegex.test((value || '').replace(/[- ]/g, ''))) {
|
|
2157
|
+
return false;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
// check to see if input value looks like a social security number
|
|
2161
|
+
var ssnRegex = /(^\d{3}-?\d{2}-?\d{4}$)/;
|
|
2162
|
+
if (ssnRegex.test(value)) {
|
|
2163
|
+
return false;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
return true;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
var AUTOCAPTURE_CONFIG_KEY = 'autocapture';
|
|
2171
|
+
var LEGACY_PAGEVIEW_CONFIG_KEY = 'track_pageview';
|
|
2172
|
+
|
|
2173
|
+
var PAGEVIEW_OPTION_FULL_URL = 'full-url';
|
|
2174
|
+
var PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING = 'url-with-path-and-query-string';
|
|
2175
|
+
var PAGEVIEW_OPTION_URL_WITH_PATH = 'url-with-path';
|
|
2176
|
+
|
|
2177
|
+
var CONFIG_BLOCK_SELECTORS = 'block_selectors';
|
|
2178
|
+
var CONFIG_BLOCK_URL_REGEXES = 'block_url_regexes';
|
|
2179
|
+
var CONFIG_CAPTURE_TEXT_CONTENT = 'capture_text_content';
|
|
2180
|
+
var CONFIG_TRACK_CLICK = 'click';
|
|
2181
|
+
var CONFIG_TRACK_INPUT = 'input';
|
|
2182
|
+
var CONFIG_TRACK_PAGEVIEW = 'pageview';
|
|
2183
|
+
var CONFIG_TRACK_SCROLL = 'scroll';
|
|
2184
|
+
var CONFIG_TRACK_SUBMIT = 'submit';
|
|
2185
|
+
|
|
2186
|
+
var CONFIG_DEFAULTS = {};
|
|
2187
|
+
CONFIG_DEFAULTS[CONFIG_CAPTURE_TEXT_CONTENT] = false;
|
|
2188
|
+
CONFIG_DEFAULTS[CONFIG_TRACK_CLICK] = true;
|
|
2189
|
+
CONFIG_DEFAULTS[CONFIG_TRACK_INPUT] = true;
|
|
2190
|
+
CONFIG_DEFAULTS[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
|
|
2191
|
+
CONFIG_DEFAULTS[CONFIG_TRACK_SCROLL] = true;
|
|
2192
|
+
CONFIG_DEFAULTS[CONFIG_TRACK_SUBMIT] = true;
|
|
2193
|
+
|
|
2194
|
+
var DEFAULT_PROPS = {
|
|
2195
|
+
'$mp_autocapture': true
|
|
2196
|
+
};
|
|
2197
|
+
|
|
2198
|
+
var MP_EV_CLICK = '$mp_click';
|
|
2199
|
+
var MP_EV_INPUT = '$mp_input_change';
|
|
2200
|
+
var MP_EV_SCROLL = '$mp_scroll';
|
|
2201
|
+
var MP_EV_SUBMIT = '$mp_submit';
|
|
2202
|
+
|
|
2203
|
+
/**
|
|
2204
|
+
* Autocapture: manages automatic event tracking
|
|
2205
|
+
* @constructor
|
|
2206
|
+
*/
|
|
2207
|
+
var Autocapture = function(mp) {
|
|
2208
|
+
this.mp = mp;
|
|
2209
|
+
};
|
|
2210
|
+
|
|
2211
|
+
Autocapture.prototype.init = function() {
|
|
2212
|
+
if (!minDOMApisSupported()) {
|
|
2213
|
+
logger$3.critical('Autocapture unavailable: missing required DOM APIs');
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
this.initPageviewTracking();
|
|
2218
|
+
this.initClickTracking();
|
|
2219
|
+
this.initInputTracking();
|
|
2220
|
+
this.initScrollTracking();
|
|
2221
|
+
this.initSubmitTracking();
|
|
2222
|
+
};
|
|
2223
|
+
|
|
2224
|
+
Autocapture.prototype.getFullConfig = function() {
|
|
2225
|
+
var autocaptureConfig = this.mp.get_config(AUTOCAPTURE_CONFIG_KEY);
|
|
2226
|
+
if (!autocaptureConfig) {
|
|
2227
|
+
// Autocapture is completely off
|
|
2228
|
+
return {};
|
|
2229
|
+
} else if (_.isObject(autocaptureConfig)) {
|
|
2230
|
+
return _.extend({}, CONFIG_DEFAULTS, autocaptureConfig);
|
|
2231
|
+
} else {
|
|
2232
|
+
// Autocapture config is non-object truthy value, return default
|
|
2233
|
+
return CONFIG_DEFAULTS;
|
|
2234
|
+
}
|
|
2235
|
+
};
|
|
2236
|
+
|
|
2237
|
+
Autocapture.prototype.getConfig = function(key) {
|
|
2238
|
+
return this.getFullConfig()[key];
|
|
2239
|
+
};
|
|
2240
|
+
|
|
2241
|
+
Autocapture.prototype.currentUrlBlocked = function() {
|
|
2242
|
+
var blockUrlRegexes = this.getConfig(CONFIG_BLOCK_URL_REGEXES) || [];
|
|
2243
|
+
if (!blockUrlRegexes || !blockUrlRegexes.length) {
|
|
2244
|
+
return false;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
var currentUrl = _.info.currentUrl();
|
|
2248
|
+
for (var i = 0; i < blockUrlRegexes.length; i++) {
|
|
2249
|
+
try {
|
|
2250
|
+
if (currentUrl.match(blockUrlRegexes[i])) {
|
|
2251
|
+
return true;
|
|
2252
|
+
}
|
|
2253
|
+
} catch (err) {
|
|
2254
|
+
logger$3.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
|
|
2255
|
+
return true;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
return false;
|
|
2259
|
+
};
|
|
2260
|
+
|
|
2261
|
+
Autocapture.prototype.pageviewTrackingConfig = function() {
|
|
2262
|
+
// supports both autocapture config and old track_pageview config
|
|
2263
|
+
if (_.isObject(this.mp.get_config(AUTOCAPTURE_CONFIG_KEY))) {
|
|
2264
|
+
return this.getConfig(CONFIG_TRACK_PAGEVIEW);
|
|
2265
|
+
} else {
|
|
2266
|
+
return this.mp.get_config(LEGACY_PAGEVIEW_CONFIG_KEY);
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
|
|
2270
|
+
// helper for event handlers
|
|
2271
|
+
Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
|
|
2272
|
+
if (this.currentUrlBlocked()) {
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
var props = getPropsForDOMEvent(
|
|
2277
|
+
ev,
|
|
2278
|
+
this.getConfig(CONFIG_BLOCK_SELECTORS),
|
|
2279
|
+
this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT)
|
|
2280
|
+
);
|
|
2281
|
+
if (props) {
|
|
2282
|
+
_.extend(props, DEFAULT_PROPS);
|
|
2283
|
+
this.mp.track(mpEventName, props);
|
|
2284
|
+
}
|
|
2285
|
+
};
|
|
2286
|
+
|
|
2287
|
+
Autocapture.prototype.initClickTracking = function() {
|
|
2288
|
+
win.removeEventListener(EV_CLICK, this.listenerClick);
|
|
2289
|
+
|
|
2290
|
+
if (!this.getConfig(CONFIG_TRACK_CLICK)) {
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
logger$3.log('Initializing click tracking');
|
|
2294
|
+
|
|
2295
|
+
this.listenerClick = win.addEventListener(EV_CLICK, function(ev) {
|
|
2296
|
+
this.trackDomEvent(ev, MP_EV_CLICK);
|
|
2297
|
+
}.bind(this));
|
|
2298
|
+
};
|
|
2299
|
+
|
|
2300
|
+
Autocapture.prototype.initInputTracking = function() {
|
|
2301
|
+
win.removeEventListener(EV_CHANGE, this.listenerChange);
|
|
2302
|
+
|
|
2303
|
+
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2306
|
+
logger$3.log('Initializing input tracking');
|
|
2307
|
+
|
|
2308
|
+
this.listenerChange = win.addEventListener(EV_CHANGE, function(ev) {
|
|
2309
|
+
this.trackDomEvent(ev, MP_EV_INPUT);
|
|
2310
|
+
}.bind(this));
|
|
2311
|
+
};
|
|
2312
|
+
|
|
2313
|
+
Autocapture.prototype.initPageviewTracking = function() {
|
|
2314
|
+
win.removeEventListener(EV_POPSTATE, this.listenerPopstate);
|
|
2315
|
+
win.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
|
|
2316
|
+
win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
|
|
2317
|
+
|
|
2318
|
+
if (!this.pageviewTrackingConfig()) {
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2321
|
+
logger$3.log('Initializing pageview tracking');
|
|
2322
|
+
|
|
2323
|
+
var previousTrackedUrl = '';
|
|
2324
|
+
var tracked = this.mp.track_pageview(DEFAULT_PROPS);
|
|
2325
|
+
if (tracked) {
|
|
2326
|
+
previousTrackedUrl = _.info.currentUrl();
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
this.listenerPopstate = win.addEventListener(EV_POPSTATE, function() {
|
|
2330
|
+
win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
|
|
2331
|
+
});
|
|
2332
|
+
this.listenerHashchange = win.addEventListener(EV_HASHCHANGE, function() {
|
|
2333
|
+
win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
|
|
2334
|
+
});
|
|
2335
|
+
var nativePushState = win.history.pushState;
|
|
2336
|
+
if (typeof nativePushState === 'function') {
|
|
2337
|
+
win.history.pushState = function(state, unused, url) {
|
|
2338
|
+
nativePushState.call(win.history, state, unused, url);
|
|
2339
|
+
win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
var nativeReplaceState = win.history.replaceState;
|
|
2343
|
+
if (typeof nativeReplaceState === 'function') {
|
|
2344
|
+
win.history.replaceState = function(state, unused, url) {
|
|
2345
|
+
nativeReplaceState.call(win.history, state, unused, url);
|
|
2346
|
+
win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
this.listenerLocationchange = win.addEventListener(EV_MP_LOCATION_CHANGE, safewrap(function() {
|
|
2350
|
+
var currentUrl = _.info.currentUrl();
|
|
2351
|
+
var shouldTrack = false;
|
|
2352
|
+
var trackPageviewOption = this.pageviewTrackingConfig();
|
|
2353
|
+
if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
|
|
2354
|
+
shouldTrack = currentUrl !== previousTrackedUrl;
|
|
2355
|
+
} else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
|
|
2356
|
+
shouldTrack = currentUrl.split('#')[0] !== previousTrackedUrl.split('#')[0];
|
|
2357
|
+
} else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH) {
|
|
2358
|
+
shouldTrack = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
if (shouldTrack) {
|
|
2362
|
+
var tracked = this.mp.track_pageview(DEFAULT_PROPS);
|
|
2363
|
+
if (tracked) {
|
|
2364
|
+
previousTrackedUrl = currentUrl;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
}.bind(this)));
|
|
2368
|
+
};
|
|
2369
|
+
|
|
2370
|
+
Autocapture.prototype.initScrollTracking = function() {
|
|
2371
|
+
win.removeEventListener(EV_SCROLL, this.listenerScroll);
|
|
2372
|
+
|
|
2373
|
+
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
2374
|
+
return;
|
|
2375
|
+
}
|
|
2376
|
+
logger$3.log('Initializing scroll tracking');
|
|
2377
|
+
|
|
2378
|
+
this.listenerScroll = win.addEventListener(EV_SCROLL, safewrap(function() {
|
|
2379
|
+
if (this.currentUrlBlocked()) {
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
var scrollTop = win.scrollY;
|
|
2384
|
+
var props = _.extend({'$scroll_top': scrollTop}, DEFAULT_PROPS);
|
|
2385
|
+
try {
|
|
2386
|
+
var scrollHeight = document$1.body.scrollHeight;
|
|
2387
|
+
var scrollPercentage = Math.round((scrollTop / (scrollHeight - win.innerHeight)) * 100);
|
|
2388
|
+
props['$scroll_height'] = scrollHeight;
|
|
2389
|
+
props['$scroll_percentage'] = scrollPercentage;
|
|
2390
|
+
} catch (err) {
|
|
2391
|
+
logger$3.critical('Error while calculating scroll percentage', err);
|
|
2392
|
+
}
|
|
2393
|
+
this.mp.track(MP_EV_SCROLL, props);
|
|
2394
|
+
}.bind(this)));
|
|
2395
|
+
};
|
|
2396
|
+
|
|
2397
|
+
Autocapture.prototype.initSubmitTracking = function() {
|
|
2398
|
+
win.removeEventListener(EV_SUBMIT, this.listenerSubmit);
|
|
2399
|
+
|
|
2400
|
+
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
logger$3.log('Initializing submit tracking');
|
|
2404
|
+
|
|
2405
|
+
this.listenerSubmit = win.addEventListener(EV_SUBMIT, function(ev) {
|
|
2406
|
+
this.trackDomEvent(ev, MP_EV_SUBMIT);
|
|
2407
|
+
}.bind(this));
|
|
2408
|
+
};
|
|
2409
|
+
|
|
2410
|
+
safewrapClass(Autocapture);
|
|
2411
|
+
|
|
1732
2412
|
/* eslint camelcase: "off" */
|
|
1733
2413
|
|
|
1734
2414
|
/**
|
|
@@ -2047,11 +2727,13 @@ var logger$1 = console_with_prefix('batch');
|
|
|
2047
2727
|
var RequestQueue = function(storageKey, options) {
|
|
2048
2728
|
options = options || {};
|
|
2049
2729
|
this.storageKey = storageKey;
|
|
2050
|
-
this.
|
|
2730
|
+
this.usePersistence = options.usePersistence;
|
|
2731
|
+
if (this.usePersistence) {
|
|
2732
|
+
this.storage = options.storage || window.localStorage;
|
|
2733
|
+
this.lock = new SharedLock(storageKey, {storage: this.storage});
|
|
2734
|
+
}
|
|
2051
2735
|
this.reportError = options.errorReporter || _.bind(logger$1.error, logger$1);
|
|
2052
|
-
this.lock = new SharedLock(storageKey, {storage: this.storage});
|
|
2053
2736
|
|
|
2054
|
-
this.usePersistence = options.usePersistence;
|
|
2055
2737
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
2056
2738
|
|
|
2057
2739
|
this.memQueue = [];
|
|
@@ -2523,7 +3205,12 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
2523
3205
|
this.flush();
|
|
2524
3206
|
} else if (
|
|
2525
3207
|
_.isObject(res) &&
|
|
2526
|
-
(
|
|
3208
|
+
(
|
|
3209
|
+
res.httpStatusCode >= 500
|
|
3210
|
+
|| res.httpStatusCode === 429
|
|
3211
|
+
|| (res.httpStatusCode <= 0 && !isOnline())
|
|
3212
|
+
|| res.error === 'timeout'
|
|
3213
|
+
)
|
|
2527
3214
|
) {
|
|
2528
3215
|
// network or API error, or 429 Too Many Requests, retry
|
|
2529
3216
|
var retryMS = this.flushInterval * 2;
|
|
@@ -4199,6 +4886,7 @@ var DEFAULT_CONFIG = {
|
|
|
4199
4886
|
'api_transport': 'XHR',
|
|
4200
4887
|
'api_payload_format': PAYLOAD_TYPE_BASE64,
|
|
4201
4888
|
'app_host': 'https://mixpanel.com',
|
|
4889
|
+
'autocapture': false,
|
|
4202
4890
|
'cdn': 'https://cdn.mxpnl.com',
|
|
4203
4891
|
'cross_site_cookie': false,
|
|
4204
4892
|
'cross_subdomain_cookie': true,
|
|
@@ -4243,10 +4931,10 @@ var DEFAULT_CONFIG = {
|
|
|
4243
4931
|
'record_block_selector': 'img, video',
|
|
4244
4932
|
'record_collect_fonts': false,
|
|
4245
4933
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
4246
|
-
'record_inline_images': false,
|
|
4247
4934
|
'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
|
|
4248
4935
|
'record_mask_text_selector': '*',
|
|
4249
4936
|
'record_max_ms': MAX_RECORDING_MS,
|
|
4937
|
+
'record_min_ms': 0,
|
|
4250
4938
|
'record_sessions_percent': 0,
|
|
4251
4939
|
'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js'
|
|
4252
4940
|
};
|
|
@@ -4457,10 +5145,8 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
4457
5145
|
}, '');
|
|
4458
5146
|
}
|
|
4459
5147
|
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
this._init_url_change_tracking(track_pageview_option);
|
|
4463
|
-
}
|
|
5148
|
+
this.autocapture = new Autocapture(this);
|
|
5149
|
+
this.autocapture.init();
|
|
4464
5150
|
|
|
4465
5151
|
if (this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent')) {
|
|
4466
5152
|
this.start_session_recording();
|
|
@@ -4495,15 +5181,35 @@ MixpanelLib.prototype.stop_session_recording = function () {
|
|
|
4495
5181
|
|
|
4496
5182
|
MixpanelLib.prototype.get_session_recording_properties = function () {
|
|
4497
5183
|
var props = {};
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
props['$mp_replay_id'] = replay_id;
|
|
4502
|
-
}
|
|
5184
|
+
var replay_id = this._get_session_replay_id();
|
|
5185
|
+
if (replay_id) {
|
|
5186
|
+
props['$mp_replay_id'] = replay_id;
|
|
4503
5187
|
}
|
|
4504
5188
|
return props;
|
|
4505
5189
|
};
|
|
4506
5190
|
|
|
5191
|
+
MixpanelLib.prototype.get_session_replay_url = function () {
|
|
5192
|
+
var replay_url = null;
|
|
5193
|
+
var replay_id = this._get_session_replay_id();
|
|
5194
|
+
if (replay_id) {
|
|
5195
|
+
var query_params = _.HTTPBuildQuery({
|
|
5196
|
+
'replay_id': replay_id,
|
|
5197
|
+
'distinct_id': this.get_distinct_id(),
|
|
5198
|
+
'token': this.get_config('token')
|
|
5199
|
+
});
|
|
5200
|
+
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
5201
|
+
}
|
|
5202
|
+
return replay_url;
|
|
5203
|
+
};
|
|
5204
|
+
|
|
5205
|
+
MixpanelLib.prototype._get_session_replay_id = function () {
|
|
5206
|
+
var replay_id = null;
|
|
5207
|
+
if (this._recorder) {
|
|
5208
|
+
replay_id = this._recorder['replayId'];
|
|
5209
|
+
}
|
|
5210
|
+
return replay_id || null;
|
|
5211
|
+
};
|
|
5212
|
+
|
|
4507
5213
|
// Private methods
|
|
4508
5214
|
|
|
4509
5215
|
MixpanelLib.prototype._loaded = function() {
|
|
@@ -4565,55 +5271,6 @@ MixpanelLib.prototype._track_dom = function(DomClass, args) {
|
|
|
4565
5271
|
return dt.track.apply(dt, args);
|
|
4566
5272
|
};
|
|
4567
5273
|
|
|
4568
|
-
MixpanelLib.prototype._init_url_change_tracking = function(track_pageview_option) {
|
|
4569
|
-
var previous_tracked_url = '';
|
|
4570
|
-
var tracked = this.track_pageview();
|
|
4571
|
-
if (tracked) {
|
|
4572
|
-
previous_tracked_url = _.info.currentUrl();
|
|
4573
|
-
}
|
|
4574
|
-
|
|
4575
|
-
if (_.include(['full-url', 'url-with-path-and-query-string', 'url-with-path'], track_pageview_option)) {
|
|
4576
|
-
win.addEventListener('popstate', function() {
|
|
4577
|
-
win.dispatchEvent(new Event('mp_locationchange'));
|
|
4578
|
-
});
|
|
4579
|
-
win.addEventListener('hashchange', function() {
|
|
4580
|
-
win.dispatchEvent(new Event('mp_locationchange'));
|
|
4581
|
-
});
|
|
4582
|
-
var nativePushState = win.history.pushState;
|
|
4583
|
-
if (typeof nativePushState === 'function') {
|
|
4584
|
-
win.history.pushState = function(state, unused, url) {
|
|
4585
|
-
nativePushState.call(win.history, state, unused, url);
|
|
4586
|
-
win.dispatchEvent(new Event('mp_locationchange'));
|
|
4587
|
-
};
|
|
4588
|
-
}
|
|
4589
|
-
var nativeReplaceState = win.history.replaceState;
|
|
4590
|
-
if (typeof nativeReplaceState === 'function') {
|
|
4591
|
-
win.history.replaceState = function(state, unused, url) {
|
|
4592
|
-
nativeReplaceState.call(win.history, state, unused, url);
|
|
4593
|
-
win.dispatchEvent(new Event('mp_locationchange'));
|
|
4594
|
-
};
|
|
4595
|
-
}
|
|
4596
|
-
win.addEventListener('mp_locationchange', function() {
|
|
4597
|
-
var current_url = _.info.currentUrl();
|
|
4598
|
-
var should_track = false;
|
|
4599
|
-
if (track_pageview_option === 'full-url') {
|
|
4600
|
-
should_track = current_url !== previous_tracked_url;
|
|
4601
|
-
} else if (track_pageview_option === 'url-with-path-and-query-string') {
|
|
4602
|
-
should_track = current_url.split('#')[0] !== previous_tracked_url.split('#')[0];
|
|
4603
|
-
} else if (track_pageview_option === 'url-with-path') {
|
|
4604
|
-
should_track = current_url.split('#')[0].split('?')[0] !== previous_tracked_url.split('#')[0].split('?')[0];
|
|
4605
|
-
}
|
|
4606
|
-
|
|
4607
|
-
if (should_track) {
|
|
4608
|
-
var tracked = this.track_pageview();
|
|
4609
|
-
if (tracked) {
|
|
4610
|
-
previous_tracked_url = current_url;
|
|
4611
|
-
}
|
|
4612
|
-
}
|
|
4613
|
-
}.bind(this));
|
|
4614
|
-
}
|
|
4615
|
-
};
|
|
4616
|
-
|
|
4617
5274
|
/**
|
|
4618
5275
|
* _prepare_callback() should be called by callers of _send_request for use
|
|
4619
5276
|
* as the callback argument.
|
|
@@ -6230,6 +6887,7 @@ MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.protot
|
|
|
6230
6887
|
MixpanelLib.prototype['start_session_recording'] = MixpanelLib.prototype.start_session_recording;
|
|
6231
6888
|
MixpanelLib.prototype['stop_session_recording'] = MixpanelLib.prototype.stop_session_recording;
|
|
6232
6889
|
MixpanelLib.prototype['get_session_recording_properties'] = MixpanelLib.prototype.get_session_recording_properties;
|
|
6890
|
+
MixpanelLib.prototype['get_session_replay_url'] = MixpanelLib.prototype.get_session_replay_url;
|
|
6233
6891
|
MixpanelLib.prototype['DEFAULT_API_ROUTES'] = DEFAULT_API_ROUTES;
|
|
6234
6892
|
|
|
6235
6893
|
// MixpanelPersistence Exports
|