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