mixpanel-browser 2.70.0 → 2.71.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/build.sh +1 -0
  3. package/dist/mixpanel-core.cjs.d.ts +440 -0
  4. package/dist/mixpanel-core.cjs.js +847 -50
  5. package/dist/mixpanel-recorder.js +5 -3
  6. package/dist/mixpanel-recorder.min.js +1 -1
  7. package/dist/mixpanel-recorder.min.js.map +1 -1
  8. package/dist/mixpanel-with-async-recorder.cjs.d.ts +440 -0
  9. package/dist/mixpanel-with-async-recorder.cjs.js +847 -50
  10. package/dist/mixpanel-with-recorder.d.ts +440 -0
  11. package/dist/mixpanel-with-recorder.js +851 -52
  12. package/dist/mixpanel-with-recorder.min.d.ts +440 -0
  13. package/dist/mixpanel-with-recorder.min.js +1 -1
  14. package/dist/mixpanel.amd.d.ts +440 -0
  15. package/dist/mixpanel.amd.js +851 -52
  16. package/dist/mixpanel.cjs.d.ts +440 -0
  17. package/dist/mixpanel.cjs.js +851 -52
  18. package/dist/mixpanel.globals.js +847 -50
  19. package/dist/mixpanel.min.js +170 -153
  20. package/dist/mixpanel.module.d.ts +440 -0
  21. package/dist/mixpanel.module.js +851 -52
  22. package/dist/mixpanel.umd.d.ts +440 -0
  23. package/dist/mixpanel.umd.js +851 -52
  24. package/dist/rrweb-compiled.js +4 -2
  25. package/package.json +2 -19
  26. package/rollup.config.mjs +28 -4
  27. package/src/autocapture/deadclick.js +254 -0
  28. package/src/autocapture/index.js +237 -41
  29. package/src/autocapture/shadow-dom-observer.js +100 -0
  30. package/src/autocapture/utils.js +230 -3
  31. package/src/config.js +1 -1
  32. package/src/flags/index.js +32 -12
  33. package/src/index.d.ts +16 -3
  34. package/src/loaders/loader-module-core.d.ts +1 -0
  35. package/src/loaders/loader-module-with-async-recorder.d.ts +1 -0
  36. package/src/loaders/loader-module.d.ts +1 -0
  37. package/src/utils.js +15 -0
@@ -1596,7 +1596,8 @@
1596
1596
  week: true,
1597
1597
  textarea: true,
1598
1598
  select: true,
1599
- password: true
1599
+ password: true,
1600
+ hidden: true
1600
1601
  } : maskAllInputs === false ? {
1601
1602
  password: true
1602
1603
  } : maskAllInputs;
@@ -13238,7 +13239,8 @@
13238
13239
  week: true,
13239
13240
  textarea: true,
13240
13241
  select: true,
13241
- password: true
13242
+ password: true,
13243
+ hidden: true
13242
13244
  } : _maskInputOptions !== void 0 ? _maskInputOptions : {
13243
13245
  password: true
13244
13246
  };
@@ -14042,7 +14044,7 @@
14042
14044
 
14043
14045
  var Config = {
14044
14046
  DEBUG: false,
14045
- LIB_VERSION: '2.70.0'
14047
+ LIB_VERSION: '2.71.0'
14046
14048
  };
14047
14049
 
14048
14050
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -15721,6 +15723,20 @@
15721
15723
  return maxlen ? guid.substring(0, maxlen) : guid;
15722
15724
  };
15723
15725
 
15726
+ /**
15727
+ * Generates a W3C traceparent header for easy interop with distributed tracing systems i.e Open Telemetry
15728
+ * https://www.w3.org/TR/trace-context/#traceparent-header
15729
+ */
15730
+ var generateTraceparent = function() {
15731
+ var traceID = _.UUID().replace(/-/g, '');
15732
+ var parentID = _.UUID().replace(/-/g, '').substring(0, 16);
15733
+
15734
+ // Sampled trace
15735
+ var traceFlags = '01';
15736
+
15737
+ return '00-' + traceID + '-' + parentID + '-' + traceFlags;
15738
+ };
15739
+
15724
15740
  // naive way to extract domain name (example.com) from full hostname (my.sub.example.com)
15725
15741
  var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i;
15726
15742
  // this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk
@@ -17856,11 +17872,17 @@
17856
17872
  var EV_CHANGE = 'change';
17857
17873
  var EV_CLICK = 'click';
17858
17874
  var EV_HASHCHANGE = 'hashchange';
17875
+ var EV_INPUT = 'input';
17876
+ var EV_LOAD = 'load';
17859
17877
  var EV_MP_LOCATION_CHANGE = 'mp_locationchange';
17860
17878
  var EV_POPSTATE = 'popstate';
17861
17879
  // TODO scrollend isn't available in Safari: document or polyfill?
17862
17880
  var EV_SCROLLEND = 'scrollend';
17881
+ var EV_SCROLL = 'scroll';
17882
+ var EV_SELECT = 'select';
17863
17883
  var EV_SUBMIT = 'submit';
17884
+ var EV_TOGGLE = 'toggle';
17885
+ var EV_VISIBILITYCHANGE = 'visibilitychange';
17864
17886
 
17865
17887
  var CLICK_EVENT_PROPS = [
17866
17888
  'clientX', 'clientY',
@@ -17877,6 +17899,77 @@
17877
17899
  'href', 'name', 'role', 'title', 'type'
17878
17900
  ];
17879
17901
 
17902
+ var INTERACTIVE_ARIA_ROLES = {
17903
+ 'button': true,
17904
+ 'checkbox': true,
17905
+ 'combobox': true,
17906
+ 'grid': true,
17907
+ 'link': true,
17908
+ 'listbox': true,
17909
+ 'menu': true,
17910
+ 'menubar': true,
17911
+ 'menuitem': true,
17912
+ 'menuitemcheckbox': true,
17913
+ 'menuitemradio': true,
17914
+ 'navigation': true,
17915
+ 'option': true,
17916
+ 'radio': true,
17917
+ 'radiogroup': true,
17918
+ 'searchbox': true,
17919
+ 'slider': true,
17920
+ 'spinbutton': true,
17921
+ 'switch': true,
17922
+ 'tab': true,
17923
+ 'tablist': true,
17924
+ 'textbox': true,
17925
+ 'tree': true,
17926
+ 'treegrid': true,
17927
+ 'treeitem': true
17928
+ };
17929
+
17930
+ var ALWAYS_NON_INTERACTIVE_TAGS = {
17931
+ // Document metadata
17932
+ 'base': true,
17933
+ 'head': true,
17934
+ 'html': true,
17935
+ 'link': true,
17936
+ 'meta': true,
17937
+ 'script': true,
17938
+ 'style': true,
17939
+ 'title': true,
17940
+ // Text formatting
17941
+ 'br': true,
17942
+ 'hr': true,
17943
+ 'wbr': true,
17944
+ // Other
17945
+ 'noscript': true,
17946
+ 'picture': true,
17947
+ 'source': true,
17948
+ 'template': true,
17949
+ 'track': true
17950
+ };
17951
+
17952
+ // Common container tags that need additional checks
17953
+ var TEXT_CONTAINER_TAGS = {
17954
+ 'article': true,
17955
+ 'div': true,
17956
+ 'h1': true,
17957
+ 'h2': true,
17958
+ 'h3': true,
17959
+ 'h4': true,
17960
+ 'h5': true,
17961
+ 'h6': true,
17962
+ 'p': true,
17963
+ 'section': true,
17964
+ 'span': true
17965
+ };
17966
+
17967
+ var EVENT_HANDLER_ATTRIBUTES = [
17968
+ 'onclick', 'onmousedown', 'onmouseup', 'onpointerdown', 'onpointerup', 'ontouchend', 'ontouchstart'
17969
+ ];
17970
+
17971
+ var MAX_DEPTH = 5;
17972
+
17880
17973
  var logger$1 = console_with_prefix('autocapture');
17881
17974
 
17882
17975
 
@@ -18244,6 +18337,10 @@
18244
18337
  }
18245
18338
  }
18246
18339
 
18340
+ function weakSetSupported() {
18341
+ return typeof WeakSet !== 'undefined';
18342
+ }
18343
+
18247
18344
  /*
18248
18345
  * Check whether a DOM event should be "tracked" or if it may contain sensitive data
18249
18346
  * using a variety of heuristics.
@@ -18369,6 +18466,149 @@
18369
18466
  return true;
18370
18467
  }
18371
18468
 
18469
+ /**
18470
+ * Creates a cross-browser compatible scroll end function with appropriate event listener.
18471
+ * For browsers that support scrollend, returns the original function with scrollend event.
18472
+ * For browsers without scrollend support, returns a debounced function that triggers
18473
+ * 100ms after the last scroll event to simulate scrollend behavior.
18474
+ * @param {Function} originalFunction - The function to call when scrolling ends
18475
+ * @returns {Object} Object containing listener function and eventType string
18476
+ * @returns {Function} returns.listener - The wrapped function to use as event listener
18477
+ * @returns {string} returns.eventType - The event type to listen for ('scrollend' or 'scroll')
18478
+ */
18479
+ function getPolyfillScrollEndFunction(originalFunction) {
18480
+ var supportsScrollEnd = 'onscrollend' in win;
18481
+ var polyfillFunction = safewrap(originalFunction);
18482
+ var polyfillEvent = EV_SCROLLEND;
18483
+ if (!supportsScrollEnd) {
18484
+ // Polyfill for browsers without scrollend support: wait 100ms after the last scroll event
18485
+ // https://developer.chrome.com/blog/scrollend-a-new-javascript-event
18486
+ var scrollTimer = null;
18487
+ var scrollDelayMs = 100;
18488
+
18489
+ polyfillFunction = safewrap(function() {
18490
+ clearTimeout(scrollTimer);
18491
+ scrollTimer = setTimeout(originalFunction, scrollDelayMs);
18492
+ });
18493
+
18494
+ polyfillEvent = EV_SCROLL;
18495
+ }
18496
+
18497
+ return {
18498
+ listener: polyfillFunction,
18499
+ eventType: polyfillEvent
18500
+ };
18501
+ }
18502
+
18503
+ function hasInlineEventHandlers(element) {
18504
+ for (var i = 0; i < EVENT_HANDLER_ATTRIBUTES.length; i++) {
18505
+ if (element.hasAttribute(EVENT_HANDLER_ATTRIBUTES[i])) {
18506
+ return true;
18507
+ }
18508
+ }
18509
+ return false;
18510
+ }
18511
+
18512
+ function hasInteractiveAriaRole(element) {
18513
+ var role = element.getAttribute('role');
18514
+ if (!role) return false;
18515
+
18516
+ // Handle invalid markup where multiple roles might be specified
18517
+ // Only the first token is recognized per ARIA spec
18518
+ var primaryRole = role.trim().split(/\s+/)[0].toLowerCase();
18519
+
18520
+ return INTERACTIVE_ARIA_ROLES[primaryRole];
18521
+ }
18522
+
18523
+ function hasAnyInteractivityIndicators(element) {
18524
+ var tagName = element.tagName.toLowerCase();
18525
+
18526
+ // Check for interactive HTML elements
18527
+ if (tagName === 'button' ||
18528
+ tagName === 'input' ||
18529
+ tagName === 'select' ||
18530
+ tagName === 'textarea' ||
18531
+ tagName === 'details' ||
18532
+ tagName === 'dialog') {
18533
+ return true;
18534
+ }
18535
+
18536
+ if (element.isContentEditable) {
18537
+ return true;
18538
+ }
18539
+
18540
+ if (element.onclick || element.onmousedown || element.onmouseup || element.ontouchstart || element.ontouchend) {
18541
+ return true;
18542
+ }
18543
+
18544
+ if (hasInlineEventHandlers(element)) {
18545
+ return true;
18546
+ }
18547
+
18548
+ if (hasInteractiveAriaRole(element)) {
18549
+ return true;
18550
+ }
18551
+
18552
+ if (tagName === 'a' && element.hasAttribute('href')) {
18553
+ return true;
18554
+ }
18555
+
18556
+ if (element.hasAttribute('tabindex')) {
18557
+ return true;
18558
+ }
18559
+
18560
+ return false;
18561
+ }
18562
+
18563
+
18564
+ function isDefinitelyNonInteractive(element) {
18565
+ if (!element || !element.tagName) {
18566
+ return true;
18567
+ }
18568
+
18569
+ var tagName = element.tagName.toLowerCase();
18570
+
18571
+ // These tags are definitely non-interactive
18572
+ if (ALWAYS_NON_INTERACTIVE_TAGS[tagName]) {
18573
+ return true;
18574
+ }
18575
+
18576
+ // For all other elements, we can only be certain they're non-interactive if they lack ALL indicators of interactivity
18577
+ // Check for any signs of interactivity
18578
+ if (hasAnyInteractivityIndicators(element)) {
18579
+ return false;
18580
+ }
18581
+
18582
+ // Check parent chain for interactive context
18583
+ var parent = element.parentElement;
18584
+ var depth = 0;
18585
+
18586
+ while (parent && depth < MAX_DEPTH) {
18587
+ if (hasAnyInteractivityIndicators(parent)) {
18588
+ return false; // Element is inside an interactive parent
18589
+ }
18590
+
18591
+ if (parent.getRootNode && parent.getRootNode() !== document$1) {
18592
+ var root = parent.getRootNode();
18593
+ if (root.host && hasAnyInteractivityIndicators(root.host)) {
18594
+ return false; // Inside an interactive shadow host
18595
+ }
18596
+ }
18597
+
18598
+ parent = parent.parentElement;
18599
+ depth++;
18600
+ }
18601
+
18602
+ // Pure text containers without any interactive context
18603
+ if (TEXT_CONTAINER_TAGS[tagName]) {
18604
+ // These are non-interactive ONLY if they have no interactive indicators (already checked as part of hasAnyInteractivityIndicators)
18605
+ return true;
18606
+ }
18607
+
18608
+ // Default: we can't be certain it's non-interactive
18609
+ return false;
18610
+ }
18611
+
18372
18612
  /** @const */ var DEFAULT_RAGE_CLICK_THRESHOLD_PX = 30;
18373
18613
  /** @const */ var DEFAULT_RAGE_CLICK_TIMEOUT_MS = 1000;
18374
18614
  /** @const */ var DEFAULT_RAGE_CLICK_CLICK_COUNT = 4;
@@ -18401,6 +18641,350 @@
18401
18641
  return false;
18402
18642
  };
18403
18643
 
18644
+ function ShadowDOMObserver(changeCallback, observerConfig) {
18645
+ this.changeCallback = changeCallback || function() {};
18646
+ this.observerConfig = observerConfig;
18647
+
18648
+ this.observedShadowRoots = null;
18649
+ this.shadowObservers = [];
18650
+ }
18651
+
18652
+ ShadowDOMObserver.prototype.getEventTarget = function(event) {
18653
+ if (!this.observedShadowRoots) {
18654
+ return;
18655
+ }
18656
+ var path = this.getComposedPath(event);
18657
+ if (path && path.length) {
18658
+ return path[0];
18659
+ }
18660
+
18661
+ return event['target'] || event['srcElement'];
18662
+ };
18663
+
18664
+
18665
+ ShadowDOMObserver.prototype.getComposedPath = function(event) {
18666
+ if ('composedPath' in event) {
18667
+ return event['composedPath']();
18668
+ }
18669
+
18670
+ return [];
18671
+ };
18672
+ ShadowDOMObserver.prototype.observeFromEvent = function(event) {
18673
+ if (!this.observedShadowRoots) {
18674
+ return;
18675
+ }
18676
+
18677
+ var path = this.getComposedPath(event);
18678
+
18679
+ // Check each element in path for shadow roots
18680
+ for (var i = 0; i < path.length; i++) {
18681
+ var element = path[i];
18682
+
18683
+ if (element && element.shadowRoot) {
18684
+ this.observeShadowRoot(element.shadowRoot);
18685
+ }
18686
+ }
18687
+ };
18688
+
18689
+
18690
+ ShadowDOMObserver.prototype.observeShadowRoot = function(shadowRoot) {
18691
+ if (!this.observedShadowRoots || this.observedShadowRoots.has(shadowRoot)) {
18692
+ return;
18693
+ }
18694
+
18695
+ var self = this;
18696
+
18697
+ try {
18698
+ this.observedShadowRoots.add(shadowRoot);
18699
+
18700
+ var observer = new window.MutationObserver(function() {
18701
+ self.changeCallback();
18702
+ });
18703
+
18704
+ observer.observe(shadowRoot, this.observerConfig);
18705
+ this.shadowObservers.push(observer);
18706
+ } catch (e) {
18707
+ logger$1.critical('Error while observing shadow root', e);
18708
+ }
18709
+ };
18710
+
18711
+
18712
+ ShadowDOMObserver.prototype.start = function() {
18713
+ if (this.observedShadowRoots) {
18714
+ return;
18715
+ }
18716
+
18717
+ if (!weakSetSupported()) {
18718
+ logger$1.critical('Shadow DOM observation unavailable: WeakSet not supported');
18719
+ return;
18720
+ }
18721
+
18722
+ this.observedShadowRoots = new WeakSet();
18723
+ };
18724
+
18725
+ ShadowDOMObserver.prototype.stop = function() {
18726
+ if (!this.observedShadowRoots) {
18727
+ return;
18728
+ }
18729
+
18730
+ for (var i = 0; i < this.shadowObservers.length; i++) {
18731
+ try {
18732
+ this.shadowObservers[i].disconnect();
18733
+ } catch (e) {
18734
+ logger$1.critical('Error while disconnecting shadow DOM observer', e);
18735
+ }
18736
+ }
18737
+ this.shadowObservers = [];
18738
+ this.observedShadowRoots = null;
18739
+ };
18740
+
18741
+ /** @const */ var DEFAULT_DEAD_CLICK_TIMEOUT_MS = 500;
18742
+ /** @const */ var INTERACTION_EVENTS = [EV_CHANGE, EV_INPUT, EV_SUBMIT, EV_SELECT, EV_TOGGLE];
18743
+ /** @const */ var LAYOUT_EVENTS = [EV_SCROLLEND];
18744
+ /** @const */ var NAVIGATION_EVENTS = [EV_HASHCHANGE];
18745
+ /** @const */ var MUTATION_OBSERVER_CONFIG = {
18746
+ characterData: true,
18747
+ childList: true,
18748
+ subtree: true,
18749
+ attributes: true,
18750
+ attributeFilter: ['style', 'class', 'hidden', 'checked', 'selected', 'value', 'display', 'visibility']
18751
+ };
18752
+
18753
+
18754
+ function DeadClickTracker(onDeadClickCallback) {
18755
+ this.eventListeners = [];
18756
+ this.mutationObserver = null;
18757
+ this.shadowDOMObserver = null;
18758
+
18759
+ this.isTracking = false;
18760
+ this.lastChangeEventTimestamp = 0;
18761
+ this.pendingClicks = [];
18762
+ this.onDeadClickCallback = onDeadClickCallback;
18763
+ this.processingActive = false;
18764
+ this.processingTimeout = null;
18765
+ }
18766
+
18767
+
18768
+ DeadClickTracker.prototype.addClick = function(event) {
18769
+ var element = this.shadowDOMObserver && this.shadowDOMObserver.getEventTarget(event);
18770
+
18771
+ if (!element) {
18772
+ element = event['target'] || event['srcElement'];
18773
+ }
18774
+
18775
+ if (!element || isDefinitelyNonInteractive(element)) {
18776
+ return false;
18777
+ }
18778
+
18779
+ if (this.shadowDOMObserver) {
18780
+ this.shadowDOMObserver.observeFromEvent(event);
18781
+ }
18782
+ this.pendingClicks.push({
18783
+ element: element,
18784
+ event: event,
18785
+ timestamp: Date.now()
18786
+ });
18787
+ return true;
18788
+ };
18789
+
18790
+ DeadClickTracker.prototype.trackClick = function(event, config) {
18791
+ if (!this.isTracking) {
18792
+ return false;
18793
+ }
18794
+
18795
+ var added = this.addClick(event);
18796
+ if (added) {
18797
+ this.triggerProcessing(config);
18798
+ }
18799
+ return added;
18800
+ };
18801
+
18802
+ DeadClickTracker.prototype.getDeadClicks = function(config) {
18803
+ if (this.pendingClicks.length === 0) {
18804
+ return [];
18805
+ }
18806
+
18807
+ var timeoutMs = config['timeout_ms'];
18808
+ var now = Date.now();
18809
+ var clicksToEvaluate = this.pendingClicks.slice(); // Copy array
18810
+ this.pendingClicks = []; // Clear original
18811
+
18812
+ var deadClicks = [];
18813
+
18814
+ for (var i = 0; i < clicksToEvaluate.length; i++) {
18815
+ var click = clicksToEvaluate[i];
18816
+
18817
+ if (now - click.timestamp >= timeoutMs) {
18818
+ // Click has exceeded timeout, check if it's dead by looking for changes after this specific click
18819
+ if (!this.hasChangesAfter(click.timestamp)) {
18820
+ deadClicks.push(click);
18821
+ }
18822
+ } else {
18823
+ // Still pending - add back
18824
+ this.pendingClicks.push(click);
18825
+ }
18826
+ }
18827
+
18828
+ return deadClicks;
18829
+ };
18830
+
18831
+ DeadClickTracker.prototype.hasChangesAfter = function(timestamp) {
18832
+ // 100ms tolerance for race condition between when we record the click and the change event
18833
+ return this.lastChangeEventTimestamp >= (timestamp - 100);
18834
+ };
18835
+
18836
+ DeadClickTracker.prototype.recordChangeEvent = function() {
18837
+ this.lastChangeEventTimestamp = Date.now();
18838
+ };
18839
+
18840
+ DeadClickTracker.prototype.triggerProcessing = function(config) {
18841
+ // Prevent multiple concurrent processing chains
18842
+ if (this.processingActive) {
18843
+ return;
18844
+ }
18845
+ this.processingActive = true;
18846
+ this.processRecursively(config);
18847
+ };
18848
+
18849
+ DeadClickTracker.prototype.processRecursively = function(config) {
18850
+ if (!this.isTracking || !this.onDeadClickCallback) {
18851
+ this.processingActive = false;
18852
+ return;
18853
+ }
18854
+
18855
+ var timeoutMs = config['timeout_ms'];
18856
+ var self = this;
18857
+
18858
+ this.processingTimeout = setTimeout(function() {
18859
+ if (!self.processingActive) {
18860
+ return;
18861
+ }
18862
+
18863
+ var deadClicks = self.getDeadClicks(config);
18864
+
18865
+ for (var i = 0; i < deadClicks.length; i++) {
18866
+ self.onDeadClickCallback(deadClicks[i].event);
18867
+ }
18868
+
18869
+ if (self.pendingClicks.length > 0) {
18870
+ self.processRecursively(config);
18871
+ } else {
18872
+ self.processingActive = false;
18873
+ }
18874
+ }, timeoutMs);
18875
+ };
18876
+
18877
+ DeadClickTracker.prototype.startTracking = function() {
18878
+ if (this.isTracking) {
18879
+ return;
18880
+ }
18881
+
18882
+ this.isTracking = true;
18883
+
18884
+ var self = this;
18885
+
18886
+ INTERACTION_EVENTS.forEach(function(event) {
18887
+ var handler = function() {
18888
+ self.recordChangeEvent();
18889
+ };
18890
+ document.addEventListener(event, handler, { capture: true, passive: true });
18891
+ self.eventListeners.push({ target: document, event: event, handler: handler, options: { capture: true, passive: true } });
18892
+ });
18893
+ NAVIGATION_EVENTS.forEach(function(event) {
18894
+ var handler = function() {
18895
+ self.recordChangeEvent();
18896
+ };
18897
+ window.addEventListener(event, handler);
18898
+ self.eventListeners.push({ target: window, event: event, handler: handler });
18899
+ });
18900
+ LAYOUT_EVENTS.forEach(function(event) {
18901
+ var handler = function() {
18902
+ self.recordChangeEvent();
18903
+ };
18904
+ window.addEventListener(event, handler, { passive: true });
18905
+ self.eventListeners.push({ target: window, event: event, handler: handler, options: { passive: true } });
18906
+ });
18907
+ var selectionHandler = function() {
18908
+ self.recordChangeEvent();
18909
+ };
18910
+ document.addEventListener('selectionchange', selectionHandler);
18911
+ self.eventListeners.push({ target: document, event: 'selectionchange', handler: selectionHandler });
18912
+
18913
+ // Set up MutationObserver
18914
+ if (window.MutationObserver) {
18915
+ try {
18916
+ this.mutationObserver = new window.MutationObserver(function() {
18917
+ self.recordChangeEvent();
18918
+ });
18919
+
18920
+ this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
18921
+ } catch (e) {
18922
+ logger$1.critical('Error while setting up mutation observer', e);
18923
+ }
18924
+ }
18925
+
18926
+ // Set up Shadow DOM observer
18927
+ if (window.customElements) {
18928
+ try {
18929
+ this.shadowDOMObserver = new ShadowDOMObserver(
18930
+ function() {
18931
+ self.recordChangeEvent();
18932
+ },
18933
+ MUTATION_OBSERVER_CONFIG
18934
+ );
18935
+ this.shadowDOMObserver.start();
18936
+ } catch (e) {
18937
+ logger$1.critical('Error while setting up shadow DOM observer', e);
18938
+ this.shadowDOMObserver = null;
18939
+ }
18940
+ }
18941
+ };
18942
+
18943
+ DeadClickTracker.prototype.stopTracking = function() {
18944
+ if (!this.isTracking) {
18945
+ return;
18946
+ }
18947
+
18948
+ this.isTracking = false;
18949
+ this.pendingClicks = [];
18950
+ this.lastChangeEventTimestamp = 0;
18951
+ this.processingActive = false;
18952
+
18953
+ if (this.processingTimeout) {
18954
+ clearTimeout(this.processingTimeout);
18955
+ this.processingTimeout = null;
18956
+ }
18957
+
18958
+ // Remove all event listeners
18959
+ for (var i = 0; i < this.eventListeners.length; i++) {
18960
+ var listener = this.eventListeners[i];
18961
+ try {
18962
+ listener.target.removeEventListener(listener.event, listener.handler, listener.options);
18963
+ } catch (e) {
18964
+ logger$1.critical('Error while removing event listener', e);
18965
+ }
18966
+ }
18967
+ this.eventListeners = [];
18968
+
18969
+ if (this.mutationObserver) {
18970
+ try {
18971
+ this.mutationObserver.disconnect();
18972
+ } catch (e) {
18973
+ logger$1.critical('Error while disconnecting mutation observer', e);
18974
+ }
18975
+ this.mutationObserver = null;
18976
+ }
18977
+
18978
+ if (this.shadowDOMObserver) {
18979
+ try {
18980
+ this.shadowDOMObserver.stop();
18981
+ } catch (e) {
18982
+ logger$1.critical('Error while stopping shadow DOM observer', e);
18983
+ }
18984
+ this.shadowDOMObserver = null;
18985
+ }
18986
+ };
18987
+
18404
18988
  var AUTOCAPTURE_CONFIG_KEY = 'autocapture';
18405
18989
  var LEGACY_PAGEVIEW_CONFIG_KEY = 'track_pageview';
18406
18990
 
@@ -18420,10 +19004,12 @@
18420
19004
  var CONFIG_SCROLL_CAPTURE_ALL = 'scroll_capture_all';
18421
19005
  var CONFIG_SCROLL_CHECKPOINTS = 'scroll_depth_percent_checkpoints';
18422
19006
  var CONFIG_TRACK_CLICK = 'click';
19007
+ var CONFIG_TRACK_DEAD_CLICK = 'dead_click';
18423
19008
  var CONFIG_TRACK_INPUT = 'input';
18424
19009
  var CONFIG_TRACK_PAGEVIEW = 'pageview';
18425
19010
  var CONFIG_TRACK_RAGE_CLICK = 'rage_click';
18426
19011
  var CONFIG_TRACK_SCROLL = 'scroll';
19012
+ var CONFIG_TRACK_PAGE_LEAVE = 'page_leave';
18427
19013
  var CONFIG_TRACK_SUBMIT = 'submit';
18428
19014
 
18429
19015
  var CONFIG_DEFAULTS$1 = {};
@@ -18438,10 +19024,12 @@
18438
19024
  CONFIG_DEFAULTS$1[CONFIG_SCROLL_CAPTURE_ALL] = false;
18439
19025
  CONFIG_DEFAULTS$1[CONFIG_SCROLL_CHECKPOINTS] = [25, 50, 75, 100];
18440
19026
  CONFIG_DEFAULTS$1[CONFIG_TRACK_CLICK] = true;
19027
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_DEAD_CLICK] = true;
18441
19028
  CONFIG_DEFAULTS$1[CONFIG_TRACK_INPUT] = true;
18442
19029
  CONFIG_DEFAULTS$1[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
18443
19030
  CONFIG_DEFAULTS$1[CONFIG_TRACK_RAGE_CLICK] = true;
18444
19031
  CONFIG_DEFAULTS$1[CONFIG_TRACK_SCROLL] = true;
19032
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_PAGE_LEAVE] = false;
18445
19033
  CONFIG_DEFAULTS$1[CONFIG_TRACK_SUBMIT] = true;
18446
19034
 
18447
19035
  var DEFAULT_PROPS = {
@@ -18449,10 +19037,12 @@
18449
19037
  };
18450
19038
 
18451
19039
  var MP_EV_CLICK = '$mp_click';
19040
+ var MP_EV_DEAD_CLICK = '$mp_dead_click';
18452
19041
  var MP_EV_INPUT = '$mp_input_change';
18453
19042
  var MP_EV_RAGE_CLICK = '$mp_rage_click';
18454
19043
  var MP_EV_SCROLL = '$mp_scroll';
18455
19044
  var MP_EV_SUBMIT = '$mp_submit';
19045
+ var MP_EV_PAGE_LEAVE = '$mp_page_leave';
18456
19046
 
18457
19047
  /**
18458
19048
  * Autocapture: manages automatic event tracking
@@ -18460,6 +19050,9 @@
18460
19050
  */
18461
19051
  var Autocapture = function(mp) {
18462
19052
  this.mp = mp;
19053
+ this.maxScrollViewDepth = 0;
19054
+ this.hasTrackedScrollSession = false;
19055
+ this.previousScrollHeight = 0;
18463
19056
  };
18464
19057
 
18465
19058
  Autocapture.prototype.init = function() {
@@ -18467,13 +19060,15 @@
18467
19060
  logger$1.critical('Autocapture unavailable: missing required DOM APIs');
18468
19061
  return;
18469
19062
  }
18470
-
19063
+ this.initPageListeners();
18471
19064
  this.initPageviewTracking();
18472
19065
  this.initClickTracking();
19066
+ this.initDeadClickTracking();
18473
19067
  this.initInputTracking();
18474
19068
  this.initScrollTracking();
18475
19069
  this.initSubmitTracking();
18476
19070
  this.initRageClickTracking();
19071
+ this.initPageLeaveTracking();
18477
19072
  };
18478
19073
 
18479
19074
  Autocapture.prototype.getFullConfig = function() {
@@ -18554,7 +19149,8 @@
18554
19149
 
18555
19150
  var isCapturedForHeatMap = this.mp.is_recording_heatmap_data() && (
18556
19151
  (mpEventName === MP_EV_CLICK && !this.getConfig(CONFIG_TRACK_CLICK)) ||
18557
- (mpEventName === MP_EV_RAGE_CLICK && !this._getRageClickConfig())
19152
+ (mpEventName === MP_EV_RAGE_CLICK && !this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK)) ||
19153
+ (mpEventName === MP_EV_DEAD_CLICK && !this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK))
18558
19154
  );
18559
19155
 
18560
19156
  var props = getPropsForDOMEvent(ev, {
@@ -18573,11 +19169,45 @@
18573
19169
  }
18574
19170
  };
18575
19171
 
18576
- Autocapture.prototype._getRageClickConfig = function() {
18577
- var config = this.getConfig(CONFIG_TRACK_RAGE_CLICK);
19172
+ Autocapture.prototype.initPageListeners = function() {
19173
+ win.removeEventListener(EV_POPSTATE, this.listenerPopstate);
19174
+ win.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
19175
+
19176
+ if (!this.pageviewTrackingConfig() && !this.getConfig(CONFIG_TRACK_PAGE_LEAVE) && !this.mp.get_config('record_heatmap_data')) {
19177
+ // These are all the configs that use these listeners
19178
+ return;
19179
+ }
19180
+
19181
+ this.listenerPopstate = function() {
19182
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
19183
+ };
19184
+ this.listenerHashchange = function() {
19185
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
19186
+ };
19187
+
19188
+ win.addEventListener(EV_POPSTATE, this.listenerPopstate);
19189
+ win.addEventListener(EV_HASHCHANGE, this.listenerHashchange);
19190
+ var nativePushState = win.history.pushState;
19191
+ if (typeof nativePushState === 'function') {
19192
+ win.history.pushState = function(state, unused, url) {
19193
+ nativePushState.call(win.history, state, unused, url);
19194
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
19195
+ };
19196
+ }
19197
+ var nativeReplaceState = win.history.replaceState;
19198
+ if (typeof nativeReplaceState === 'function') {
19199
+ win.history.replaceState = function(state, unused, url) {
19200
+ nativeReplaceState.call(win.history, state, unused, url);
19201
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
19202
+ };
19203
+ }
19204
+ };
19205
+
19206
+ Autocapture.prototype._getClickTrackingConfig = function(configKey) {
19207
+ var config = this.getConfig(configKey);
18578
19208
 
18579
19209
  if (!config) {
18580
- return null; // rage click tracking disabled
19210
+ return null; // click tracking disabled
18581
19211
  }
18582
19212
 
18583
19213
  if (config === true) {
@@ -18591,6 +19221,68 @@
18591
19221
  return {}; // fallback to defaults for any other truthy value
18592
19222
  };
18593
19223
 
19224
+ Autocapture.prototype._trackPageLeave = function(ev, currentUrl, currentScrollHeight) {
19225
+ if (this.hasTrackedScrollSession) {
19226
+ // User has navigated away already ending their impression.
19227
+ return;
19228
+ }
19229
+ this.hasTrackedScrollSession = true;
19230
+ var viewportHeight = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
19231
+ var scrollPercentage = Math.round(Math.max(this.maxScrollViewDepth - viewportHeight, 0) / (currentScrollHeight - viewportHeight) * 100);
19232
+ var foldLinePercentage = Math.round((viewportHeight / currentScrollHeight) * 100);
19233
+ if (currentScrollHeight <= viewportHeight) {
19234
+ // If the content fits within the viewport, consider it fully scrolled
19235
+ scrollPercentage = 100;
19236
+ foldLinePercentage = 100;
19237
+ }
19238
+
19239
+ var props = _.extend({
19240
+ '$max_scroll_view_depth': this.maxScrollViewDepth,
19241
+ '$max_scroll_percentage': scrollPercentage,
19242
+ '$fold_line_percentage': foldLinePercentage,
19243
+ '$scroll_height': currentScrollHeight,
19244
+ '$event_type': ev.type,
19245
+ '$current_url': currentUrl || _.info.currentUrl(),
19246
+ '$viewportHeight': viewportHeight, // This is the fold line
19247
+ '$viewportWidth': Math.max(document$1.documentElement.clientWidth, win.innerWidth || 0),
19248
+ }, DEFAULT_PROPS);
19249
+
19250
+ if (this.mp.is_recording_heatmap_data() && !this.getConfig(CONFIG_TRACK_PAGE_LEAVE)) {
19251
+ props['$captured_for_heatmap'] = true;
19252
+ }
19253
+
19254
+ // Send with beacon transport to ensure event is sent before unload
19255
+ this.mp.track(MP_EV_PAGE_LEAVE, props, {transport: 'sendBeacon'});
19256
+ };
19257
+
19258
+ Autocapture.prototype._initScrollDepthTracking = function() {
19259
+ win.removeEventListener(EV_SCROLL, this.listenerScrollDepth);
19260
+ win.removeEventListener(EV_SCROLLEND, this.listenerScrollDepth);
19261
+
19262
+ if (!this.mp.get_config('record_heatmap_data')) {
19263
+ return;
19264
+ }
19265
+
19266
+ logger$1.log('Initializing scroll depth tracking');
19267
+
19268
+ this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
19269
+
19270
+ var updateScrollDepth = function() {
19271
+ if (this.currentUrlBlocked()) {
19272
+ return;
19273
+ }
19274
+ var scrollViewHeight = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0) + win.scrollY;
19275
+ if (scrollViewHeight > this.maxScrollViewDepth) {
19276
+ this.maxScrollViewDepth = scrollViewHeight;
19277
+ }
19278
+ this.previousScrollHeight = document$1.body.scrollHeight;
19279
+ }.bind(this);
19280
+
19281
+ var scrollEndPolyfill = getPolyfillScrollEndFunction(updateScrollDepth);
19282
+ this.listenerScrollDepth = scrollEndPolyfill.listener;
19283
+ win.addEventListener(scrollEndPolyfill.eventType, this.listenerScrollDepth);
19284
+ };
19285
+
18594
19286
  Autocapture.prototype.initClickTracking = function() {
18595
19287
  win.removeEventListener(EV_CLICK, this.listenerClick);
18596
19288
 
@@ -18599,12 +19291,49 @@
18599
19291
  }
18600
19292
  logger$1.log('Initializing click tracking');
18601
19293
 
18602
- this.listenerClick = win.addEventListener(EV_CLICK, function(ev) {
19294
+ this.listenerClick = function(ev) {
18603
19295
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
18604
19296
  return;
18605
19297
  }
18606
19298
  this.trackDomEvent(ev, MP_EV_CLICK);
18607
- }.bind(this));
19299
+ }.bind(this);
19300
+ win.addEventListener(EV_CLICK, this.listenerClick);
19301
+ };
19302
+
19303
+ Autocapture.prototype.initDeadClickTracking = function() {
19304
+ var deadClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK);
19305
+
19306
+ if (!deadClickConfig && !this.mp.get_config('record_heatmap_data')) {
19307
+ this.stopDeadClickTracking();
19308
+ return;
19309
+ }
19310
+
19311
+ logger$1.log('Initializing dead click tracking');
19312
+ if (!this._deadClickTracker) {
19313
+ this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
19314
+ this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
19315
+ }.bind(this));
19316
+ this._deadClickTracker.startTracking();
19317
+ }
19318
+
19319
+ if (!this.listenerDeadClick) {
19320
+ this.listenerDeadClick = function(ev) {
19321
+ var currentDeadClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK);
19322
+ if (!currentDeadClickConfig && !this.mp.is_recording_heatmap_data()) {
19323
+ return;
19324
+ }
19325
+ if (this.currentUrlBlocked()) {
19326
+ return;
19327
+ }
19328
+ // Normalize config to ensure timeout_ms is always set
19329
+ var normalizedConfig = currentDeadClickConfig || {};
19330
+ if (!normalizedConfig['timeout_ms']) {
19331
+ normalizedConfig['timeout_ms'] = DEFAULT_DEAD_CLICK_TIMEOUT_MS;
19332
+ }
19333
+ this._deadClickTracker.trackClick(ev, normalizedConfig);
19334
+ }.bind(this);
19335
+ win.addEventListener(EV_CLICK, this.listenerDeadClick);
19336
+ }
18608
19337
  };
18609
19338
 
18610
19339
  Autocapture.prototype.initInputTracking = function() {
@@ -18615,17 +19344,16 @@
18615
19344
  }
18616
19345
  logger$1.log('Initializing input tracking');
18617
19346
 
18618
- this.listenerChange = win.addEventListener(EV_CHANGE, function(ev) {
19347
+ this.listenerChange = function(ev) {
18619
19348
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
18620
19349
  return;
18621
19350
  }
18622
19351
  this.trackDomEvent(ev, MP_EV_INPUT);
18623
- }.bind(this));
19352
+ }.bind(this);
19353
+ win.addEventListener(EV_CHANGE, this.listenerChange);
18624
19354
  };
18625
19355
 
18626
19356
  Autocapture.prototype.initPageviewTracking = function() {
18627
- win.removeEventListener(EV_POPSTATE, this.listenerPopstate);
18628
- win.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
18629
19357
  win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
18630
19358
 
18631
19359
  if (!this.pageviewTrackingConfig()) {
@@ -18642,27 +19370,7 @@
18642
19370
  previousTrackedUrl = _.info.currentUrl();
18643
19371
  }
18644
19372
 
18645
- this.listenerPopstate = win.addEventListener(EV_POPSTATE, function() {
18646
- win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
18647
- });
18648
- this.listenerHashchange = win.addEventListener(EV_HASHCHANGE, function() {
18649
- win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
18650
- });
18651
- var nativePushState = win.history.pushState;
18652
- if (typeof nativePushState === 'function') {
18653
- win.history.pushState = function(state, unused, url) {
18654
- nativePushState.call(win.history, state, unused, url);
18655
- win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
18656
- };
18657
- }
18658
- var nativeReplaceState = win.history.replaceState;
18659
- if (typeof nativeReplaceState === 'function') {
18660
- win.history.replaceState = function(state, unused, url) {
18661
- nativeReplaceState.call(win.history, state, unused, url);
18662
- win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
18663
- };
18664
- }
18665
- this.listenerLocationchange = win.addEventListener(EV_MP_LOCATION_CHANGE, safewrap(function() {
19373
+ this.listenerLocationchange = safewrap(function() {
18666
19374
  if (this.currentUrlBlocked()) {
18667
19375
  return;
18668
19376
  }
@@ -18689,13 +19397,14 @@
18689
19397
  logger$1.log('Path change: re-initializing scroll depth checkpoints');
18690
19398
  }
18691
19399
  }
18692
- }.bind(this)));
19400
+ }.bind(this));
19401
+ win.addEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
18693
19402
  };
18694
19403
 
18695
19404
  Autocapture.prototype.initRageClickTracking = function() {
18696
19405
  win.removeEventListener(EV_CLICK, this.listenerRageClick);
18697
19406
 
18698
- var rageClickConfig = this._getRageClickConfig();
19407
+ var rageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
18699
19408
  if (!rageClickConfig && !this.mp.get_config('record_heatmap_data')) {
18700
19409
  return;
18701
19410
  }
@@ -18706,7 +19415,7 @@
18706
19415
  }
18707
19416
 
18708
19417
  this.listenerRageClick = function(ev) {
18709
- var currentRageClickConfig = this._getRageClickConfig();
19418
+ var currentRageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
18710
19419
  if (!currentRageClickConfig && !this.mp.is_recording_heatmap_data()) {
18711
19420
  return;
18712
19421
  }
@@ -18724,6 +19433,8 @@
18724
19433
 
18725
19434
  Autocapture.prototype.initScrollTracking = function() {
18726
19435
  win.removeEventListener(EV_SCROLLEND, this.listenerScroll);
19436
+ win.removeEventListener(EV_SCROLL, this.listenerScroll);
19437
+
18727
19438
 
18728
19439
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
18729
19440
  return;
@@ -18731,7 +19442,7 @@
18731
19442
  logger$1.log('Initializing scroll tracking');
18732
19443
  this.lastScrollCheckpoint = 0;
18733
19444
 
18734
- this.listenerScroll = win.addEventListener(EV_SCROLLEND, safewrap(function() {
19445
+ var scrollTrackFunction = function() {
18735
19446
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
18736
19447
  return;
18737
19448
  }
@@ -18770,7 +19481,11 @@
18770
19481
  if (shouldTrack) {
18771
19482
  this.mp.track(MP_EV_SCROLL, props);
18772
19483
  }
18773
- }.bind(this)));
19484
+ }.bind(this);
19485
+
19486
+ var scrollEndPolyfill = getPolyfillScrollEndFunction(scrollTrackFunction);
19487
+ this.listenerScroll = scrollEndPolyfill.listener;
19488
+ win.addEventListener(scrollEndPolyfill.eventType, this.listenerScroll);
18774
19489
  };
18775
19490
 
18776
19491
  Autocapture.prototype.initSubmitTracking = function() {
@@ -18781,18 +19496,81 @@
18781
19496
  }
18782
19497
  logger$1.log('Initializing submit tracking');
18783
19498
 
18784
- this.listenerSubmit = win.addEventListener(EV_SUBMIT, function(ev) {
19499
+ this.listenerSubmit = function(ev) {
18785
19500
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
18786
19501
  return;
18787
19502
  }
18788
19503
  this.trackDomEvent(ev, MP_EV_SUBMIT);
19504
+ }.bind(this);
19505
+ win.addEventListener(EV_SUBMIT, this.listenerSubmit);
19506
+ };
19507
+
19508
+ Autocapture.prototype.initPageLeaveTracking = function() {
19509
+ // Capture page_leave both when the user navigates away from the page (visibilitychange) as well
19510
+ // as when they navigate to a different page within the SPA (popstate/pushstate/hashchange).
19511
+ document$1.removeEventListener(EV_VISIBILITYCHANGE, this.listenerPageLeaveVisibilitychange);
19512
+ win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerPageLeaveLocationchange);
19513
+ win.removeEventListener(EV_LOAD, this.listenerPageLoad);
19514
+
19515
+ if (!this.getConfig(CONFIG_TRACK_PAGE_LEAVE) && !this.mp.get_config('record_heatmap_data')) {
19516
+ return;
19517
+ }
19518
+
19519
+ logger$1.log('Initializing page visibility tracking.');
19520
+ this._initScrollDepthTracking();
19521
+ var previousTrackedUrl = _.info.currentUrl();
19522
+
19523
+ // Initialize previousScrollHeight on `load` which handles async loading
19524
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event
19525
+ this.listenerPageLoad = function() {
19526
+ this.previousScrollHeight = document$1.body.scrollHeight;
19527
+ }.bind(this);
19528
+ win.addEventListener(EV_LOAD, this.listenerPageLoad);
19529
+
19530
+ // Track page navigation events similar to how initPageviewTracking does it
19531
+ this.listenerPageLeaveLocationchange = safewrap(function(ev) {
19532
+ if (this.currentUrlBlocked()) {
19533
+ return;
19534
+ }
19535
+
19536
+ var currentUrl = _.info.currentUrl();
19537
+ // Track all URL changes including query string or fragment changes as separate scroll sessions
19538
+ var shouldTrack = currentUrl !== previousTrackedUrl;
19539
+
19540
+ if (shouldTrack) {
19541
+ this._trackPageLeave(ev, previousTrackedUrl, this.previousScrollHeight);
19542
+ previousTrackedUrl = currentUrl;
19543
+ // Fragment navigation should call scroll(end) and trigger listener, don't add window.scrollY here.
19544
+ this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
19545
+ this.previousScrollHeight = document$1.body.scrollHeight;
19546
+ this.hasTrackedScrollSession = false;
19547
+ }
18789
19548
  }.bind(this));
19549
+ win.addEventListener(EV_MP_LOCATION_CHANGE, this.listenerPageLeaveLocationchange);
19550
+
19551
+ this.listenerPageLeaveVisibilitychange = function(ev) {
19552
+ if (document$1.hidden) {
19553
+ this._trackPageLeave(ev, previousTrackedUrl, this.previousScrollHeight);
19554
+ }
19555
+ }.bind(this);
19556
+ document$1.addEventListener(EV_VISIBILITYCHANGE, this.listenerPageLeaveVisibilitychange);
19557
+ };
19558
+
19559
+ Autocapture.prototype.stopDeadClickTracking = function() {
19560
+ if (this.listenerDeadClick) {
19561
+ win.removeEventListener(EV_CLICK, this.listenerDeadClick);
19562
+ this.listenerDeadClick = null;
19563
+ }
19564
+
19565
+ if (this._deadClickTracker) {
19566
+ this._deadClickTracker.stopTracking();
19567
+ this._deadClickTracker = null;
19568
+ }
18790
19569
  };
18791
19570
 
18792
19571
  // TODO integrate error_reporter from mixpanel instance
18793
19572
  safewrapClass(Autocapture);
18794
19573
 
18795
- var fetch = win['fetch'];
18796
19574
  var logger = console_with_prefix('flags');
18797
19575
 
18798
19576
  var FLAGS_CONFIG_KEY = 'flags';
@@ -18806,6 +19584,7 @@
18806
19584
  * @constructor
18807
19585
  */
18808
19586
  var FeatureFlagManager = function(initOptions) {
19587
+ this.fetch = win['fetch'];
18809
19588
  this.getFullApiRoute = initOptions.getFullApiRoute;
18810
19589
  this.getMpConfig = initOptions.getConfigFunc;
18811
19590
  this.setMpConfig = initOptions.setConfigFunc;
@@ -18814,7 +19593,7 @@
18814
19593
  };
18815
19594
 
18816
19595
  FeatureFlagManager.prototype.init = function() {
18817
- if (!minApisSupported()) {
19596
+ if (!this.minApisSupported()) {
18818
19597
  logger.critical('Feature Flags unavailable: missing minimum required APIs');
18819
19598
  return;
18820
19599
  }
@@ -18877,6 +19656,7 @@
18877
19656
 
18878
19657
  var distinctId = this.getMpProperty('distinct_id');
18879
19658
  var deviceId = this.getMpProperty('$device_id');
19659
+ var traceparent = generateTraceparent();
18880
19660
  logger.log('Fetching flags for distinct ID: ' + distinctId);
18881
19661
 
18882
19662
  var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
@@ -18888,10 +19668,11 @@
18888
19668
  var url = this.getFullApiRoute() + '?' + searchParams.toString();
18889
19669
 
18890
19670
  this._fetchInProgressStartTime = Date.now();
18891
- this.fetchPromise = win['fetch'](url, {
19671
+ this.fetchPromise = this.fetch.call(win, url, {
18892
19672
  'method': 'GET',
18893
19673
  'headers': {
18894
- 'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':')
19674
+ 'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
19675
+ 'traceparent': traceparent
18895
19676
  }
18896
19677
  }).then(function(response) {
18897
19678
  this.markFetchComplete();
@@ -18904,10 +19685,14 @@
18904
19685
  _.each(responseFlags, function(data, key) {
18905
19686
  flags.set(key, {
18906
19687
  'key': data['variant_key'],
18907
- 'value': data['variant_value']
19688
+ 'value': data['variant_value'],
19689
+ 'experiment_id': data['experiment_id'],
19690
+ 'is_experiment_active': data['is_experiment_active'],
19691
+ 'is_qa_tester': data['is_qa_tester']
18908
19692
  });
18909
19693
  });
18910
19694
  this.flags = flags;
19695
+ this._traceparent = traceparent;
18911
19696
  }.bind(this)).catch(function(error) {
18912
19697
  this.markFetchComplete();
18913
19698
  logger.error(error);
@@ -19004,22 +19789,36 @@
19004
19789
  return;
19005
19790
  }
19006
19791
  this.trackedFeatures.add(featureName);
19007
- this.track('$experiment_started', {
19792
+
19793
+ var trackingProperties = {
19008
19794
  'Experiment name': featureName,
19009
19795
  'Variant name': feature['key'],
19010
19796
  '$experiment_type': 'feature_flag',
19011
19797
  'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
19012
19798
  'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
19013
- 'Variant fetch latency (ms)': this._fetchLatency
19014
- });
19799
+ 'Variant fetch latency (ms)': this._fetchLatency,
19800
+ 'Variant fetch traceparent': this._traceparent,
19801
+ };
19802
+
19803
+ if (feature['experiment_id'] !== 'undefined') {
19804
+ trackingProperties['$experiment_id'] = feature['experiment_id'];
19805
+ }
19806
+ if (feature['is_experiment_active'] !== 'undefined') {
19807
+ trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
19808
+ }
19809
+ if (feature['is_qa_tester'] !== 'undefined') {
19810
+ trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
19811
+ }
19812
+
19813
+ this.track('$experiment_started', trackingProperties);
19015
19814
  };
19016
19815
 
19017
- function minApisSupported() {
19018
- return !!fetch &&
19816
+ FeatureFlagManager.prototype.minApisSupported = function() {
19817
+ return !!this.fetch &&
19019
19818
  typeof Promise !== 'undefined' &&
19020
19819
  typeof Map !== 'undefined' &&
19021
19820
  typeof Set !== 'undefined';
19022
- }
19821
+ };
19023
19822
 
19024
19823
  safewrapClass(FeatureFlagManager);
19025
19824