mixpanel-browser 2.55.1 → 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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  var Config = {
4
4
  DEBUG: false,
5
- LIB_VERSION: '2.55.1'
5
+ LIB_VERSION: '2.56.0-ac-alpha-3'
6
6
  };
7
7
 
8
8
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -16,11 +16,14 @@ if (typeof(window) === 'undefined') {
16
16
  win = {
17
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) {
@@ -1738,6 +1764,651 @@ _['info']['browser'] = _.info.browser;
1738
1764
  _['info']['browserVersion'] = _.info.browserVersion;
1739
1765
  _['info']['properties'] = _.info.properties;
1740
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
+
1741
2412
  /* eslint camelcase: "off" */
1742
2413
 
1743
2414
  /**
@@ -2056,11 +2727,13 @@ var logger$1 = console_with_prefix('batch');
2056
2727
  var RequestQueue = function(storageKey, options) {
2057
2728
  options = options || {};
2058
2729
  this.storageKey = storageKey;
2059
- this.storage = options.storage || window.localStorage;
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
+ }
2060
2735
  this.reportError = options.errorReporter || _.bind(logger$1.error, logger$1);
2061
- this.lock = new SharedLock(storageKey, {storage: this.storage});
2062
2736
 
2063
- this.usePersistence = options.usePersistence;
2064
2737
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
2065
2738
 
2066
2739
  this.memQueue = [];
@@ -4213,6 +4886,7 @@ var DEFAULT_CONFIG = {
4213
4886
  'api_transport': 'XHR',
4214
4887
  'api_payload_format': PAYLOAD_TYPE_BASE64,
4215
4888
  'app_host': 'https://mixpanel.com',
4889
+ 'autocapture': false,
4216
4890
  'cdn': 'https://cdn.mxpnl.com',
4217
4891
  'cross_site_cookie': false,
4218
4892
  'cross_subdomain_cookie': true,
@@ -4257,7 +4931,6 @@ var DEFAULT_CONFIG = {
4257
4931
  'record_block_selector': 'img, video',
4258
4932
  'record_collect_fonts': false,
4259
4933
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
4260
- 'record_inline_images': false,
4261
4934
  'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
4262
4935
  'record_mask_text_selector': '*',
4263
4936
  'record_max_ms': MAX_RECORDING_MS,
@@ -4472,10 +5145,8 @@ MixpanelLib.prototype._init = function(token, config, name) {
4472
5145
  }, '');
4473
5146
  }
4474
5147
 
4475
- var track_pageview_option = this.get_config('track_pageview');
4476
- if (track_pageview_option) {
4477
- this._init_url_change_tracking(track_pageview_option);
4478
- }
5148
+ this.autocapture = new Autocapture(this);
5149
+ this.autocapture.init();
4479
5150
 
4480
5151
  if (this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent')) {
4481
5152
  this.start_session_recording();
@@ -4510,15 +5181,35 @@ MixpanelLib.prototype.stop_session_recording = function () {
4510
5181
 
4511
5182
  MixpanelLib.prototype.get_session_recording_properties = function () {
4512
5183
  var props = {};
4513
- if (this._recorder) {
4514
- var replay_id = this._recorder['replayId'];
4515
- if (replay_id) {
4516
- props['$mp_replay_id'] = replay_id;
4517
- }
5184
+ var replay_id = this._get_session_replay_id();
5185
+ if (replay_id) {
5186
+ props['$mp_replay_id'] = replay_id;
4518
5187
  }
4519
5188
  return props;
4520
5189
  };
4521
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
+
4522
5213
  // Private methods
4523
5214
 
4524
5215
  MixpanelLib.prototype._loaded = function() {
@@ -4580,55 +5271,6 @@ MixpanelLib.prototype._track_dom = function(DomClass, args) {
4580
5271
  return dt.track.apply(dt, args);
4581
5272
  };
4582
5273
 
4583
- MixpanelLib.prototype._init_url_change_tracking = function(track_pageview_option) {
4584
- var previous_tracked_url = '';
4585
- var tracked = this.track_pageview();
4586
- if (tracked) {
4587
- previous_tracked_url = _.info.currentUrl();
4588
- }
4589
-
4590
- if (_.include(['full-url', 'url-with-path-and-query-string', 'url-with-path'], track_pageview_option)) {
4591
- win.addEventListener('popstate', function() {
4592
- win.dispatchEvent(new Event('mp_locationchange'));
4593
- });
4594
- win.addEventListener('hashchange', function() {
4595
- win.dispatchEvent(new Event('mp_locationchange'));
4596
- });
4597
- var nativePushState = win.history.pushState;
4598
- if (typeof nativePushState === 'function') {
4599
- win.history.pushState = function(state, unused, url) {
4600
- nativePushState.call(win.history, state, unused, url);
4601
- win.dispatchEvent(new Event('mp_locationchange'));
4602
- };
4603
- }
4604
- var nativeReplaceState = win.history.replaceState;
4605
- if (typeof nativeReplaceState === 'function') {
4606
- win.history.replaceState = function(state, unused, url) {
4607
- nativeReplaceState.call(win.history, state, unused, url);
4608
- win.dispatchEvent(new Event('mp_locationchange'));
4609
- };
4610
- }
4611
- win.addEventListener('mp_locationchange', function() {
4612
- var current_url = _.info.currentUrl();
4613
- var should_track = false;
4614
- if (track_pageview_option === 'full-url') {
4615
- should_track = current_url !== previous_tracked_url;
4616
- } else if (track_pageview_option === 'url-with-path-and-query-string') {
4617
- should_track = current_url.split('#')[0] !== previous_tracked_url.split('#')[0];
4618
- } else if (track_pageview_option === 'url-with-path') {
4619
- should_track = current_url.split('#')[0].split('?')[0] !== previous_tracked_url.split('#')[0].split('?')[0];
4620
- }
4621
-
4622
- if (should_track) {
4623
- var tracked = this.track_pageview();
4624
- if (tracked) {
4625
- previous_tracked_url = current_url;
4626
- }
4627
- }
4628
- }.bind(this));
4629
- }
4630
- };
4631
-
4632
5274
  /**
4633
5275
  * _prepare_callback() should be called by callers of _send_request for use
4634
5276
  * as the callback argument.
@@ -6245,6 +6887,7 @@ MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.protot
6245
6887
  MixpanelLib.prototype['start_session_recording'] = MixpanelLib.prototype.start_session_recording;
6246
6888
  MixpanelLib.prototype['stop_session_recording'] = MixpanelLib.prototype.stop_session_recording;
6247
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;
6248
6891
  MixpanelLib.prototype['DEFAULT_API_ROUTES'] = DEFAULT_API_ROUTES;
6249
6892
 
6250
6893
  // MixpanelPersistence Exports