mixpanel-browser 2.58.0 → 2.59.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.
@@ -3,7 +3,7 @@
3
3
 
4
4
  var Config = {
5
5
  DEBUG: false,
6
- LIB_VERSION: '2.58.0'
6
+ LIB_VERSION: '2.59.0'
7
7
  };
8
8
 
9
9
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
@@ -15,11 +15,14 @@
15
15
  win = {
16
16
  navigator: { userAgent: '', onLine: true },
17
17
  document: {
18
+ createElement: function() { return {}; },
18
19
  location: loc,
19
20
  referrer: ''
20
21
  },
21
22
  screen: { width: 0, height: 0 },
22
- location: loc
23
+ location: loc,
24
+ addEventListener: function() {},
25
+ removeEventListener: function() {}
23
26
  };
24
27
  } else {
25
28
  win = window;
@@ -494,6 +497,29 @@
494
497
  };
495
498
 
496
499
 
500
+ var safewrap = function(f) {
501
+ return function() {
502
+ try {
503
+ return f.apply(this, arguments);
504
+ } catch (e) {
505
+ console.critical('Implementation error. Please turn on debug and contact support@mixpanel.com.');
506
+ if (Config.DEBUG){
507
+ console.critical(e);
508
+ }
509
+ }
510
+ };
511
+ };
512
+
513
+ var safewrapClass = function(klass) {
514
+ var proto = klass.prototype;
515
+ for (var func in proto) {
516
+ if (typeof(proto[func]) === 'function') {
517
+ proto[func] = safewrap(proto[func]);
518
+ }
519
+ }
520
+ };
521
+
522
+
497
523
  // UNDERSCORE
498
524
  // Embed part of the Underscore Library
499
525
  _.bind = function(func, context) {
@@ -1272,6 +1298,7 @@
1272
1298
  var BLOCKED_UA_STRS = [
1273
1299
  'ahrefsbot',
1274
1300
  'ahrefssiteaudit',
1301
+ 'amazonbot',
1275
1302
  'baiduspider',
1276
1303
  'bingbot',
1277
1304
  'bingpreview',
@@ -1281,7 +1308,7 @@
1281
1308
  'pinterest',
1282
1309
  'screaming frog',
1283
1310
  'yahoo! slurp',
1284
- 'yandexbot',
1311
+ 'yandex',
1285
1312
 
1286
1313
  // a whole bunch of goog-specific crawlers
1287
1314
  // https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers
@@ -2102,6 +2129,689 @@
2102
2129
  _['info']['properties'] = _.info.properties;
2103
2130
  _['NPO'] = NpoPromise;
2104
2131
 
2132
+ // stateless utils
2133
+
2134
+ var EV_CHANGE = 'change';
2135
+ var EV_CLICK = 'click';
2136
+ var EV_HASHCHANGE = 'hashchange';
2137
+ var EV_MP_LOCATION_CHANGE = 'mp_locationchange';
2138
+ var EV_POPSTATE = 'popstate';
2139
+ // TODO scrollend isn't available in Safari: document or polyfill?
2140
+ var EV_SCROLLEND = 'scrollend';
2141
+ var EV_SUBMIT = 'submit';
2142
+
2143
+ var CLICK_EVENT_PROPS = [
2144
+ 'clientX', 'clientY',
2145
+ 'offsetX', 'offsetY',
2146
+ 'pageX', 'pageY',
2147
+ 'screenX', 'screenY',
2148
+ 'x', 'y'
2149
+ ];
2150
+ var OPT_IN_CLASSES = ['mp-include'];
2151
+ var OPT_OUT_CLASSES = ['mp-no-track'];
2152
+ var SENSITIVE_DATA_CLASSES = OPT_OUT_CLASSES.concat(['mp-sensitive']);
2153
+ var TRACKED_ATTRS = [
2154
+ 'aria-label', 'aria-labelledby', 'aria-describedby',
2155
+ 'href', 'name', 'role', 'title', 'type'
2156
+ ];
2157
+
2158
+ var logger$3 = console_with_prefix('autocapture');
2159
+
2160
+
2161
+ function getClasses(el) {
2162
+ var classes = {};
2163
+ var classList = getClassName(el).split(' ');
2164
+ for (var i = 0; i < classList.length; i++) {
2165
+ var cls = classList[i];
2166
+ if (cls) {
2167
+ classes[cls] = true;
2168
+ }
2169
+ }
2170
+ return classes;
2171
+ }
2172
+
2173
+ /*
2174
+ * Get the className of an element, accounting for edge cases where element.className is an object
2175
+ * @param {Element} el - element to get the className of
2176
+ * @returns {string} the element's class
2177
+ */
2178
+ function getClassName(el) {
2179
+ switch(typeof el.className) {
2180
+ case 'string':
2181
+ return el.className;
2182
+ case 'object': // handle cases where className might be SVGAnimatedString or some other type
2183
+ return el.className.baseVal || el.getAttribute('class') || '';
2184
+ default: // future proof
2185
+ return '';
2186
+ }
2187
+ }
2188
+
2189
+ function getPreviousElementSibling(el) {
2190
+ if (el.previousElementSibling) {
2191
+ return el.previousElementSibling;
2192
+ } else {
2193
+ do {
2194
+ el = el.previousSibling;
2195
+ } while (el && !isElementNode(el));
2196
+ return el;
2197
+ }
2198
+ }
2199
+
2200
+ function getPropertiesFromElement(el) {
2201
+ var props = {
2202
+ '$classes': getClassName(el).split(' '),
2203
+ '$tag_name': el.tagName.toLowerCase()
2204
+ };
2205
+ var elId = el.id;
2206
+ if (elId) {
2207
+ props['$id'] = elId;
2208
+ }
2209
+
2210
+ if (shouldTrackElement(el)) {
2211
+ _.each(TRACKED_ATTRS, function(attr) {
2212
+ if (el.hasAttribute(attr)) {
2213
+ var attrVal = el.getAttribute(attr);
2214
+ if (shouldTrackValue(attrVal)) {
2215
+ props['$attr-' + attr] = attrVal;
2216
+ }
2217
+ }
2218
+ });
2219
+ }
2220
+
2221
+ var nthChild = 1;
2222
+ var nthOfType = 1;
2223
+ var currentElem = el;
2224
+ while (currentElem = getPreviousElementSibling(currentElem)) { // eslint-disable-line no-cond-assign
2225
+ nthChild++;
2226
+ if (currentElem.tagName === el.tagName) {
2227
+ nthOfType++;
2228
+ }
2229
+ }
2230
+ props['$nth_child'] = nthChild;
2231
+ props['$nth_of_type'] = nthOfType;
2232
+
2233
+ return props;
2234
+ }
2235
+
2236
+ function getPropsForDOMEvent(ev, blockSelectors, captureTextContent) {
2237
+ blockSelectors = blockSelectors || [];
2238
+ var props = null;
2239
+
2240
+ var target = typeof ev.target === 'undefined' ? ev.srcElement : ev.target;
2241
+ if (isTextNode(target)) { // defeat Safari bug (see: http://www.quirksmode.org/js/events_properties.html)
2242
+ target = target.parentNode;
2243
+ }
2244
+
2245
+ if (shouldTrackDomEvent(target, ev)) {
2246
+ var targetElementList = [target];
2247
+ var curEl = target;
2248
+ while (curEl.parentNode && !isTag(curEl, 'body')) {
2249
+ targetElementList.push(curEl.parentNode);
2250
+ curEl = curEl.parentNode;
2251
+ }
2252
+
2253
+ var elementsJson = [];
2254
+ var href, explicitNoTrack = false;
2255
+ _.each(targetElementList, function(el) {
2256
+ var shouldTrackEl = shouldTrackElement(el);
2257
+
2258
+ // if the element or a parent element is an anchor tag
2259
+ // include the href as a property
2260
+ if (el.tagName.toLowerCase() === 'a') {
2261
+ href = el.getAttribute('href');
2262
+ href = shouldTrackEl && shouldTrackValue(href) && href;
2263
+ }
2264
+
2265
+ // allow users to programmatically prevent tracking of elements by adding classes such as 'mp-no-track'
2266
+ var classes = getClasses(el);
2267
+ _.each(OPT_OUT_CLASSES, function(cls) {
2268
+ if (classes[cls]) {
2269
+ explicitNoTrack = true;
2270
+ }
2271
+ });
2272
+
2273
+ if (!explicitNoTrack) {
2274
+ // programmatically prevent tracking of elements that match CSS selectors
2275
+ _.each(blockSelectors, function(sel) {
2276
+ try {
2277
+ if (el['matches'](sel)) {
2278
+ explicitNoTrack = true;
2279
+ }
2280
+ } catch (err) {
2281
+ logger$3.critical('Error while checking selector: ' + sel, err);
2282
+ }
2283
+ });
2284
+ }
2285
+
2286
+ elementsJson.push(getPropertiesFromElement(el));
2287
+ }, this);
2288
+
2289
+ if (!explicitNoTrack) {
2290
+ var docElement = document$1['documentElement'];
2291
+ props = {
2292
+ '$event_type': ev.type,
2293
+ '$host': win.location.host,
2294
+ '$pathname': win.location.pathname,
2295
+ '$elements': elementsJson,
2296
+ '$el_attr__href': href,
2297
+ '$viewportHeight': Math.max(docElement['clientHeight'], win['innerHeight'] || 0),
2298
+ '$viewportWidth': Math.max(docElement['clientWidth'], win['innerWidth'] || 0)
2299
+ };
2300
+
2301
+ if (captureTextContent) {
2302
+ elementText = getSafeText(target);
2303
+ if (elementText && elementText.length) {
2304
+ props['$el_text'] = elementText;
2305
+ }
2306
+ }
2307
+
2308
+ if (ev.type === EV_CLICK) {
2309
+ _.each(CLICK_EVENT_PROPS, function(prop) {
2310
+ if (prop in ev) {
2311
+ props['$' + prop] = ev[prop];
2312
+ }
2313
+ });
2314
+ target = guessRealClickTarget(ev);
2315
+ }
2316
+ // prioritize text content from "real" click target if different from original target
2317
+ if (captureTextContent) {
2318
+ var elementText = getSafeText(target);
2319
+ if (elementText && elementText.length) {
2320
+ props['$el_text'] = elementText;
2321
+ }
2322
+ }
2323
+
2324
+ if (target) {
2325
+ var targetProps = getPropertiesFromElement(target);
2326
+ props['$target'] = targetProps;
2327
+ // pull up more props onto main event props
2328
+ props['$el_classes'] = targetProps['$classes'];
2329
+ _.extend(props, _.strip_empty_properties({
2330
+ '$el_id': targetProps['$id'],
2331
+ '$el_tag_name': targetProps['$tag_name']
2332
+ }));
2333
+ }
2334
+ }
2335
+ }
2336
+
2337
+ return props;
2338
+ }
2339
+
2340
+
2341
+ /*
2342
+ * Get the direct text content of an element, protecting against sensitive data collection.
2343
+ * Concats textContent of each of the element's text node children; this avoids potential
2344
+ * collection of sensitive data that could happen if we used element.textContent and the
2345
+ * element had sensitive child elements, since element.textContent includes child content.
2346
+ * Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
2347
+ * @param {Element} el - element to get the text of
2348
+ * @returns {string} the element's direct text content
2349
+ */
2350
+ function getSafeText(el) {
2351
+ var elText = '';
2352
+
2353
+ if (shouldTrackElement(el) && el.childNodes && el.childNodes.length) {
2354
+ _.each(el.childNodes, function(child) {
2355
+ if (isTextNode(child) && child.textContent) {
2356
+ elText += _.trim(child.textContent)
2357
+ // scrub potentially sensitive values
2358
+ .split(/(\s+)/).filter(shouldTrackValue).join('')
2359
+ // normalize whitespace
2360
+ .replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ')
2361
+ // truncate
2362
+ .substring(0, 255);
2363
+ }
2364
+ });
2365
+ }
2366
+
2367
+ return _.trim(elText);
2368
+ }
2369
+
2370
+ function guessRealClickTarget(ev) {
2371
+ var target = ev.target;
2372
+ var composedPath = ev['composedPath']();
2373
+ for (var i = 0; i < composedPath.length; i++) {
2374
+ var node = composedPath[i];
2375
+ if (
2376
+ isTag(node, 'a') ||
2377
+ isTag(node, 'button') ||
2378
+ isTag(node, 'input') ||
2379
+ isTag(node, 'select') ||
2380
+ (node.getAttribute && node.getAttribute('role') === 'button')
2381
+ ) {
2382
+ target = node;
2383
+ break;
2384
+ }
2385
+ if (node === target) {
2386
+ break;
2387
+ }
2388
+ }
2389
+ return target;
2390
+ }
2391
+
2392
+ /*
2393
+ * Check whether a DOM node has nodeType Node.ELEMENT_NODE
2394
+ * @param {Node} node - node to check
2395
+ * @returns {boolean} whether node is of the correct nodeType
2396
+ */
2397
+ function isElementNode(node) {
2398
+ return node && node.nodeType === 1; // Node.ELEMENT_NODE - use integer constant for browser portability
2399
+ }
2400
+
2401
+ /*
2402
+ * Check whether an element is of a given tag type.
2403
+ * Due to potential reference discrepancies (such as the webcomponents.js polyfill),
2404
+ * we want to match tagNames instead of specific references because something like
2405
+ * element === document.body won't always work because element might not be a native
2406
+ * element.
2407
+ * @param {Element} el - element to check
2408
+ * @param {string} tag - tag name (e.g., "div")
2409
+ * @returns {boolean} whether el is of the given tag type
2410
+ */
2411
+ function isTag(el, tag) {
2412
+ return el && el.tagName && el.tagName.toLowerCase() === tag.toLowerCase();
2413
+ }
2414
+
2415
+ /*
2416
+ * Check whether a DOM node is a TEXT_NODE
2417
+ * @param {Node} node - node to check
2418
+ * @returns {boolean} whether node is of type Node.TEXT_NODE
2419
+ */
2420
+ function isTextNode(node) {
2421
+ return node && node.nodeType === 3; // Node.TEXT_NODE - use integer constant for browser portability
2422
+ }
2423
+
2424
+ function minDOMApisSupported() {
2425
+ try {
2426
+ var testEl = document$1.createElement('div');
2427
+ return !!testEl['matches'];
2428
+ } catch (err) {
2429
+ return false;
2430
+ }
2431
+ }
2432
+
2433
+ /*
2434
+ * Check whether a DOM event should be "tracked" or if it may contain sensitive data
2435
+ * using a variety of heuristics.
2436
+ * @param {Element} el - element to check
2437
+ * @param {Event} ev - event to check
2438
+ * @returns {boolean} whether the event should be tracked
2439
+ */
2440
+ function shouldTrackDomEvent(el, ev) {
2441
+ if (!el || isTag(el, 'html') || !isElementNode(el)) {
2442
+ return false;
2443
+ }
2444
+ var tag = el.tagName.toLowerCase();
2445
+ switch (tag) {
2446
+ case 'form':
2447
+ return ev.type === EV_SUBMIT;
2448
+ case 'input':
2449
+ if (['button', 'submit'].indexOf(el.getAttribute('type')) === -1) {
2450
+ return ev.type === EV_CHANGE;
2451
+ } else {
2452
+ return ev.type === EV_CLICK;
2453
+ }
2454
+ case 'select':
2455
+ case 'textarea':
2456
+ return ev.type === EV_CHANGE;
2457
+ default:
2458
+ return ev.type === EV_CLICK;
2459
+ }
2460
+ }
2461
+
2462
+ /*
2463
+ * Check whether a DOM element should be "tracked" or if it may contain sensitive data
2464
+ * using a variety of heuristics.
2465
+ * @param {Element} el - element to check
2466
+ * @returns {boolean} whether the element should be tracked
2467
+ */
2468
+ function shouldTrackElement(el) {
2469
+ var i;
2470
+
2471
+ for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
2472
+ var classes = getClasses(curEl);
2473
+ for (i = 0; i < SENSITIVE_DATA_CLASSES.length; i++) {
2474
+ if (classes[SENSITIVE_DATA_CLASSES[i]]) {
2475
+ return false;
2476
+ }
2477
+ }
2478
+ }
2479
+
2480
+ var elClasses = getClasses(el);
2481
+ for (i = 0; i < OPT_IN_CLASSES.length; i++) {
2482
+ if (elClasses[OPT_IN_CLASSES[i]]) {
2483
+ return true;
2484
+ }
2485
+ }
2486
+
2487
+ // don't send data from inputs or similar elements since there will always be
2488
+ // a risk of clientside javascript placing sensitive data in attributes
2489
+ if (
2490
+ isTag(el, 'input') ||
2491
+ isTag(el, 'select') ||
2492
+ isTag(el, 'textarea') ||
2493
+ el.getAttribute('contenteditable') === 'true'
2494
+ ) {
2495
+ return false;
2496
+ }
2497
+
2498
+ // don't include hidden or password fields
2499
+ var type = el.type || '';
2500
+ 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"]
2501
+ switch(type.toLowerCase()) {
2502
+ case 'hidden':
2503
+ return false;
2504
+ case 'password':
2505
+ return false;
2506
+ }
2507
+ }
2508
+
2509
+ // filter out data from fields that look like sensitive fields
2510
+ var name = el.name || el.id || '';
2511
+ 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"]
2512
+ var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
2513
+ if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
2514
+ return false;
2515
+ }
2516
+ }
2517
+
2518
+ return true;
2519
+ }
2520
+
2521
+
2522
+ /*
2523
+ * Check whether a string value should be "tracked" or if it may contain sensitive data
2524
+ * using a variety of heuristics.
2525
+ * @param {string} value - string value to check
2526
+ * @returns {boolean} whether the element should be tracked
2527
+ */
2528
+ function shouldTrackValue(value) {
2529
+ if (value === null || _.isUndefined(value)) {
2530
+ return false;
2531
+ }
2532
+
2533
+ if (typeof value === 'string') {
2534
+ value = _.trim(value);
2535
+
2536
+ // check to see if input value looks like a credit card number
2537
+ // see: https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s20.html
2538
+ 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}))$/;
2539
+ if (ccRegex.test((value || '').replace(/[- ]/g, ''))) {
2540
+ return false;
2541
+ }
2542
+
2543
+ // check to see if input value looks like a social security number
2544
+ var ssnRegex = /(^\d{3}-?\d{2}-?\d{4}$)/;
2545
+ if (ssnRegex.test(value)) {
2546
+ return false;
2547
+ }
2548
+ }
2549
+
2550
+ return true;
2551
+ }
2552
+
2553
+ var AUTOCAPTURE_CONFIG_KEY = 'autocapture';
2554
+ var LEGACY_PAGEVIEW_CONFIG_KEY = 'track_pageview';
2555
+
2556
+ var PAGEVIEW_OPTION_FULL_URL = 'full-url';
2557
+ var PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING = 'url-with-path-and-query-string';
2558
+ var PAGEVIEW_OPTION_URL_WITH_PATH = 'url-with-path';
2559
+
2560
+ var CONFIG_BLOCK_SELECTORS = 'block_selectors';
2561
+ var CONFIG_BLOCK_URL_REGEXES = 'block_url_regexes';
2562
+ var CONFIG_CAPTURE_TEXT_CONTENT = 'capture_text_content';
2563
+ var CONFIG_TRACK_CLICK = 'click';
2564
+ var CONFIG_TRACK_INPUT = 'input';
2565
+ var CONFIG_TRACK_PAGEVIEW = 'pageview';
2566
+ var CONFIG_TRACK_SCROLL = 'scroll';
2567
+ var CONFIG_TRACK_SUBMIT = 'submit';
2568
+
2569
+ var CONFIG_DEFAULTS = {};
2570
+ CONFIG_DEFAULTS[CONFIG_CAPTURE_TEXT_CONTENT] = false;
2571
+ CONFIG_DEFAULTS[CONFIG_TRACK_CLICK] = true;
2572
+ CONFIG_DEFAULTS[CONFIG_TRACK_INPUT] = true;
2573
+ CONFIG_DEFAULTS[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
2574
+ CONFIG_DEFAULTS[CONFIG_TRACK_SCROLL] = true;
2575
+ CONFIG_DEFAULTS[CONFIG_TRACK_SUBMIT] = true;
2576
+
2577
+ var DEFAULT_PROPS = {
2578
+ '$mp_autocapture': true
2579
+ };
2580
+
2581
+ var MP_EV_CLICK = '$mp_click';
2582
+ var MP_EV_INPUT = '$mp_input_change';
2583
+ var MP_EV_SCROLL = '$mp_scroll';
2584
+ var MP_EV_SUBMIT = '$mp_submit';
2585
+
2586
+ /**
2587
+ * Autocapture: manages automatic event tracking
2588
+ * @constructor
2589
+ */
2590
+ var Autocapture = function(mp) {
2591
+ this.mp = mp;
2592
+ };
2593
+
2594
+ Autocapture.prototype.init = function() {
2595
+ if (!minDOMApisSupported()) {
2596
+ logger$3.critical('Autocapture unavailable: missing required DOM APIs');
2597
+ return;
2598
+ }
2599
+
2600
+ this.initPageviewTracking();
2601
+ this.initClickTracking();
2602
+ this.initInputTracking();
2603
+ this.initScrollTracking();
2604
+ this.initSubmitTracking();
2605
+ };
2606
+
2607
+ Autocapture.prototype.getFullConfig = function() {
2608
+ var autocaptureConfig = this.mp.get_config(AUTOCAPTURE_CONFIG_KEY);
2609
+ if (!autocaptureConfig) {
2610
+ // Autocapture is completely off
2611
+ return {};
2612
+ } else if (_.isObject(autocaptureConfig)) {
2613
+ return _.extend({}, CONFIG_DEFAULTS, autocaptureConfig);
2614
+ } else {
2615
+ // Autocapture config is non-object truthy value, return default
2616
+ return CONFIG_DEFAULTS;
2617
+ }
2618
+ };
2619
+
2620
+ Autocapture.prototype.getConfig = function(key) {
2621
+ return this.getFullConfig()[key];
2622
+ };
2623
+
2624
+ Autocapture.prototype.currentUrlBlocked = function() {
2625
+ var blockUrlRegexes = this.getConfig(CONFIG_BLOCK_URL_REGEXES) || [];
2626
+ if (!blockUrlRegexes || !blockUrlRegexes.length) {
2627
+ return false;
2628
+ }
2629
+
2630
+ var currentUrl = _.info.currentUrl();
2631
+ for (var i = 0; i < blockUrlRegexes.length; i++) {
2632
+ try {
2633
+ if (currentUrl.match(blockUrlRegexes[i])) {
2634
+ return true;
2635
+ }
2636
+ } catch (err) {
2637
+ logger$3.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
2638
+ return true;
2639
+ }
2640
+ }
2641
+ return false;
2642
+ };
2643
+
2644
+ Autocapture.prototype.pageviewTrackingConfig = function() {
2645
+ // supports both autocapture config and old track_pageview config
2646
+ if (this.mp.get_config(AUTOCAPTURE_CONFIG_KEY)) {
2647
+ return this.getConfig(CONFIG_TRACK_PAGEVIEW);
2648
+ } else {
2649
+ return this.mp.get_config(LEGACY_PAGEVIEW_CONFIG_KEY);
2650
+ }
2651
+ };
2652
+
2653
+ // helper for event handlers
2654
+ Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
2655
+ if (this.currentUrlBlocked()) {
2656
+ return;
2657
+ }
2658
+
2659
+ var props = getPropsForDOMEvent(
2660
+ ev,
2661
+ this.getConfig(CONFIG_BLOCK_SELECTORS),
2662
+ this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT)
2663
+ );
2664
+ if (props) {
2665
+ _.extend(props, DEFAULT_PROPS);
2666
+ this.mp.track(mpEventName, props);
2667
+ }
2668
+ };
2669
+
2670
+ Autocapture.prototype.initClickTracking = function() {
2671
+ win.removeEventListener(EV_CLICK, this.listenerClick);
2672
+
2673
+ if (!this.getConfig(CONFIG_TRACK_CLICK)) {
2674
+ return;
2675
+ }
2676
+ logger$3.log('Initializing click tracking');
2677
+
2678
+ this.listenerClick = win.addEventListener(EV_CLICK, function(ev) {
2679
+ if (!this.getConfig(CONFIG_TRACK_CLICK)) {
2680
+ return;
2681
+ }
2682
+ this.trackDomEvent(ev, MP_EV_CLICK);
2683
+ }.bind(this));
2684
+ };
2685
+
2686
+ Autocapture.prototype.initInputTracking = function() {
2687
+ win.removeEventListener(EV_CHANGE, this.listenerChange);
2688
+
2689
+ if (!this.getConfig(CONFIG_TRACK_INPUT)) {
2690
+ return;
2691
+ }
2692
+ logger$3.log('Initializing input tracking');
2693
+
2694
+ this.listenerChange = win.addEventListener(EV_CHANGE, function(ev) {
2695
+ if (!this.getConfig(CONFIG_TRACK_INPUT)) {
2696
+ return;
2697
+ }
2698
+ this.trackDomEvent(ev, MP_EV_INPUT);
2699
+ }.bind(this));
2700
+ };
2701
+
2702
+ Autocapture.prototype.initPageviewTracking = function() {
2703
+ win.removeEventListener(EV_POPSTATE, this.listenerPopstate);
2704
+ win.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
2705
+ win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
2706
+
2707
+ if (!this.pageviewTrackingConfig()) {
2708
+ return;
2709
+ }
2710
+ logger$3.log('Initializing pageview tracking');
2711
+
2712
+ var previousTrackedUrl = '';
2713
+ var tracked = false;
2714
+ if (!this.currentUrlBlocked()) {
2715
+ tracked = this.mp.track_pageview(DEFAULT_PROPS);
2716
+ }
2717
+ if (tracked) {
2718
+ previousTrackedUrl = _.info.currentUrl();
2719
+ }
2720
+
2721
+ this.listenerPopstate = win.addEventListener(EV_POPSTATE, function() {
2722
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
2723
+ });
2724
+ this.listenerHashchange = win.addEventListener(EV_HASHCHANGE, function() {
2725
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
2726
+ });
2727
+ var nativePushState = win.history.pushState;
2728
+ if (typeof nativePushState === 'function') {
2729
+ win.history.pushState = function(state, unused, url) {
2730
+ nativePushState.call(win.history, state, unused, url);
2731
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
2732
+ };
2733
+ }
2734
+ var nativeReplaceState = win.history.replaceState;
2735
+ if (typeof nativeReplaceState === 'function') {
2736
+ win.history.replaceState = function(state, unused, url) {
2737
+ nativeReplaceState.call(win.history, state, unused, url);
2738
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
2739
+ };
2740
+ }
2741
+ this.listenerLocationchange = win.addEventListener(EV_MP_LOCATION_CHANGE, safewrap(function() {
2742
+ if (this.currentUrlBlocked()) {
2743
+ return;
2744
+ }
2745
+
2746
+ var currentUrl = _.info.currentUrl();
2747
+ var shouldTrack = false;
2748
+ var trackPageviewOption = this.pageviewTrackingConfig();
2749
+ if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
2750
+ shouldTrack = currentUrl !== previousTrackedUrl;
2751
+ } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
2752
+ shouldTrack = currentUrl.split('#')[0] !== previousTrackedUrl.split('#')[0];
2753
+ } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH) {
2754
+ shouldTrack = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
2755
+ }
2756
+
2757
+ if (shouldTrack) {
2758
+ var tracked = this.mp.track_pageview(DEFAULT_PROPS);
2759
+ if (tracked) {
2760
+ previousTrackedUrl = currentUrl;
2761
+ }
2762
+ }
2763
+ }.bind(this)));
2764
+ };
2765
+
2766
+ Autocapture.prototype.initScrollTracking = function() {
2767
+ win.removeEventListener(EV_SCROLLEND, this.listenerScroll);
2768
+
2769
+ if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
2770
+ return;
2771
+ }
2772
+ logger$3.log('Initializing scroll tracking');
2773
+
2774
+ this.listenerScroll = win.addEventListener(EV_SCROLLEND, safewrap(function() {
2775
+ if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
2776
+ return;
2777
+ }
2778
+ if (this.currentUrlBlocked()) {
2779
+ return;
2780
+ }
2781
+
2782
+ var scrollTop = win.scrollY;
2783
+ var props = _.extend({'$scroll_top': scrollTop}, DEFAULT_PROPS);
2784
+ try {
2785
+ var scrollHeight = document$1.body.scrollHeight;
2786
+ var scrollPercentage = Math.round((scrollTop / (scrollHeight - win.innerHeight)) * 100);
2787
+ props['$scroll_height'] = scrollHeight;
2788
+ props['$scroll_percentage'] = scrollPercentage;
2789
+ } catch (err) {
2790
+ logger$3.critical('Error while calculating scroll percentage', err);
2791
+ }
2792
+ this.mp.track(MP_EV_SCROLL, props);
2793
+ }.bind(this)));
2794
+ };
2795
+
2796
+ Autocapture.prototype.initSubmitTracking = function() {
2797
+ win.removeEventListener(EV_SUBMIT, this.listenerSubmit);
2798
+
2799
+ if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
2800
+ return;
2801
+ }
2802
+ logger$3.log('Initializing submit tracking');
2803
+
2804
+ this.listenerSubmit = win.addEventListener(EV_SUBMIT, function(ev) {
2805
+ if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
2806
+ return;
2807
+ }
2808
+ this.trackDomEvent(ev, MP_EV_SUBMIT);
2809
+ }.bind(this));
2810
+ };
2811
+
2812
+ // TODO integrate error_reporter from mixpanel instance
2813
+ safewrapClass(Autocapture);
2814
+
2105
2815
  /* eslint camelcase: "off" */
2106
2816
 
2107
2817
  /**
@@ -4704,6 +5414,7 @@
4704
5414
  'api_transport': 'XHR',
4705
5415
  'api_payload_format': PAYLOAD_TYPE_BASE64,
4706
5416
  'app_host': 'https://mixpanel.com',
5417
+ 'autocapture': false,
4707
5418
  'cdn': 'https://cdn.mxpnl.com',
4708
5419
  'cross_site_cookie': false,
4709
5420
  'cross_subdomain_cookie': true,
@@ -4963,10 +5674,8 @@
4963
5674
  }, '');
4964
5675
  }
4965
5676
 
4966
- var track_pageview_option = this.get_config('track_pageview');
4967
- if (track_pageview_option) {
4968
- this._init_url_change_tracking(track_pageview_option);
4969
- }
5677
+ this.autocapture = new Autocapture(this);
5678
+ this.autocapture.init();
4970
5679
 
4971
5680
  if (this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent')) {
4972
5681
  this.start_session_recording();
@@ -5091,55 +5800,6 @@
5091
5800
  return dt.track.apply(dt, args);
5092
5801
  };
5093
5802
 
5094
- MixpanelLib.prototype._init_url_change_tracking = function(track_pageview_option) {
5095
- var previous_tracked_url = '';
5096
- var tracked = this.track_pageview();
5097
- if (tracked) {
5098
- previous_tracked_url = _.info.currentUrl();
5099
- }
5100
-
5101
- if (_.include(['full-url', 'url-with-path-and-query-string', 'url-with-path'], track_pageview_option)) {
5102
- win.addEventListener('popstate', function() {
5103
- win.dispatchEvent(new Event('mp_locationchange'));
5104
- });
5105
- win.addEventListener('hashchange', function() {
5106
- win.dispatchEvent(new Event('mp_locationchange'));
5107
- });
5108
- var nativePushState = win.history.pushState;
5109
- if (typeof nativePushState === 'function') {
5110
- win.history.pushState = function(state, unused, url) {
5111
- nativePushState.call(win.history, state, unused, url);
5112
- win.dispatchEvent(new Event('mp_locationchange'));
5113
- };
5114
- }
5115
- var nativeReplaceState = win.history.replaceState;
5116
- if (typeof nativeReplaceState === 'function') {
5117
- win.history.replaceState = function(state, unused, url) {
5118
- nativeReplaceState.call(win.history, state, unused, url);
5119
- win.dispatchEvent(new Event('mp_locationchange'));
5120
- };
5121
- }
5122
- win.addEventListener('mp_locationchange', function() {
5123
- var current_url = _.info.currentUrl();
5124
- var should_track = false;
5125
- if (track_pageview_option === 'full-url') {
5126
- should_track = current_url !== previous_tracked_url;
5127
- } else if (track_pageview_option === 'url-with-path-and-query-string') {
5128
- should_track = current_url.split('#')[0] !== previous_tracked_url.split('#')[0];
5129
- } else if (track_pageview_option === 'url-with-path') {
5130
- should_track = current_url.split('#')[0].split('?')[0] !== previous_tracked_url.split('#')[0].split('?')[0];
5131
- }
5132
-
5133
- if (should_track) {
5134
- var tracked = this.track_pageview();
5135
- if (tracked) {
5136
- previous_tracked_url = current_url;
5137
- }
5138
- }
5139
- }.bind(this));
5140
- }
5141
- };
5142
-
5143
5803
  /**
5144
5804
  * _prepare_callback() should be called by callers of _send_request for use
5145
5805
  * as the callback argument.
@@ -6397,6 +7057,10 @@
6397
7057
  this['persistence'].update_config(this['config']);
6398
7058
  }
6399
7059
  Config.DEBUG = Config.DEBUG || this.get_config('debug');
7060
+
7061
+ if ('autocapture' in config && this.autocapture) {
7062
+ this.autocapture.init();
7063
+ }
6400
7064
  }
6401
7065
  };
6402
7066