mixpanel-browser 2.70.0 → 2.71.1

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 (38) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/CHANGELOG.md +11 -0
  3. package/build.sh +1 -0
  4. package/dist/mixpanel-core.cjs.d.ts +440 -0
  5. package/dist/mixpanel-core.cjs.js +849 -50
  6. package/dist/mixpanel-recorder.js +5 -3
  7. package/dist/mixpanel-recorder.min.js +1 -1
  8. package/dist/mixpanel-recorder.min.js.map +1 -1
  9. package/dist/mixpanel-with-async-recorder.cjs.d.ts +440 -0
  10. package/dist/mixpanel-with-async-recorder.cjs.js +849 -50
  11. package/dist/mixpanel-with-recorder.d.ts +440 -0
  12. package/dist/mixpanel-with-recorder.js +853 -52
  13. package/dist/mixpanel-with-recorder.min.d.ts +440 -0
  14. package/dist/mixpanel-with-recorder.min.js +1 -1
  15. package/dist/mixpanel.amd.d.ts +440 -0
  16. package/dist/mixpanel.amd.js +853 -52
  17. package/dist/mixpanel.cjs.d.ts +440 -0
  18. package/dist/mixpanel.cjs.js +853 -52
  19. package/dist/mixpanel.globals.js +849 -50
  20. package/dist/mixpanel.min.js +170 -153
  21. package/dist/mixpanel.module.d.ts +440 -0
  22. package/dist/mixpanel.module.js +853 -52
  23. package/dist/mixpanel.umd.d.ts +440 -0
  24. package/dist/mixpanel.umd.js +853 -52
  25. package/dist/rrweb-compiled.js +4 -2
  26. package/package.json +2 -19
  27. package/rollup.config.mjs +28 -4
  28. package/src/autocapture/deadclick.js +254 -0
  29. package/src/autocapture/index.js +239 -41
  30. package/src/autocapture/shadow-dom-observer.js +100 -0
  31. package/src/autocapture/utils.js +230 -3
  32. package/src/config.js +1 -1
  33. package/src/flags/index.js +32 -12
  34. package/src/index.d.ts +16 -3
  35. package/src/loaders/loader-module-core.d.ts +1 -0
  36. package/src/loaders/loader-module-with-async-recorder.d.ts +1 -0
  37. package/src/loaders/loader-module.d.ts +1 -0
  38. 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.1'
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,70 @@ 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
+
19227
+ if (!this.getConfig(CONFIG_TRACK_PAGE_LEAVE) && !this.mp.is_recording_heatmap_data()) {
19228
+ return;
19229
+ }
19230
+
19231
+ this.hasTrackedScrollSession = true;
19232
+ var viewportHeight = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
19233
+ var scrollPercentage = Math.round(Math.max(this.maxScrollViewDepth - viewportHeight, 0) / (currentScrollHeight - viewportHeight) * 100);
19234
+ var foldLinePercentage = Math.round((viewportHeight / currentScrollHeight) * 100);
19235
+ if (currentScrollHeight <= viewportHeight) {
19236
+ // If the content fits within the viewport, consider it fully scrolled
19237
+ scrollPercentage = 100;
19238
+ foldLinePercentage = 100;
19239
+ }
19240
+
19241
+ var props = _.extend({
19242
+ '$max_scroll_view_depth': this.maxScrollViewDepth,
19243
+ '$max_scroll_percentage': scrollPercentage,
19244
+ '$fold_line_percentage': foldLinePercentage,
19245
+ '$scroll_height': currentScrollHeight,
19246
+ '$event_type': ev.type,
19247
+ '$current_url': currentUrl || _.info.currentUrl(),
19248
+ '$viewportHeight': viewportHeight, // This is the fold line
19249
+ '$viewportWidth': Math.max(document$1.documentElement.clientWidth, win.innerWidth || 0),
19250
+ '$captured_for_heatmap': this.mp.is_recording_heatmap_data()
19251
+ }, DEFAULT_PROPS);
19252
+
19253
+ // Send with beacon transport to ensure event is sent before unload
19254
+ this.mp.track(MP_EV_PAGE_LEAVE, props, {transport: 'sendBeacon'});
19255
+ };
19256
+
19257
+ Autocapture.prototype._initScrollDepthTracking = function() {
19258
+ win.removeEventListener(EV_SCROLL, this.listenerScrollDepth);
19259
+ win.removeEventListener(EV_SCROLLEND, this.listenerScrollDepth);
19260
+
19261
+ if (!this.mp.get_config('record_heatmap_data')) {
19262
+ return;
19263
+ }
19264
+
19265
+ logger$1.log('Initializing scroll depth tracking');
19266
+
19267
+ this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
19268
+
19269
+ var updateScrollDepth = function() {
19270
+ if (this.currentUrlBlocked()) {
19271
+ return;
19272
+ }
19273
+ var scrollViewHeight = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0) + win.scrollY;
19274
+ if (scrollViewHeight > this.maxScrollViewDepth) {
19275
+ this.maxScrollViewDepth = scrollViewHeight;
19276
+ }
19277
+ this.previousScrollHeight = document$1.body.scrollHeight;
19278
+ }.bind(this);
19279
+
19280
+ var scrollEndPolyfill = getPolyfillScrollEndFunction(updateScrollDepth);
19281
+ this.listenerScrollDepth = scrollEndPolyfill.listener;
19282
+ win.addEventListener(scrollEndPolyfill.eventType, this.listenerScrollDepth);
19283
+ };
19284
+
18591
19285
  Autocapture.prototype.initClickTracking = function() {
18592
19286
  win.removeEventListener(EV_CLICK, this.listenerClick);
18593
19287
 
@@ -18596,12 +19290,49 @@ Autocapture.prototype.initClickTracking = function() {
18596
19290
  }
18597
19291
  logger$1.log('Initializing click tracking');
18598
19292
 
18599
- this.listenerClick = win.addEventListener(EV_CLICK, function(ev) {
19293
+ this.listenerClick = function(ev) {
18600
19294
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
18601
19295
  return;
18602
19296
  }
18603
19297
  this.trackDomEvent(ev, MP_EV_CLICK);
18604
- }.bind(this));
19298
+ }.bind(this);
19299
+ win.addEventListener(EV_CLICK, this.listenerClick);
19300
+ };
19301
+
19302
+ Autocapture.prototype.initDeadClickTracking = function() {
19303
+ var deadClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK);
19304
+
19305
+ if (!deadClickConfig && !this.mp.get_config('record_heatmap_data')) {
19306
+ this.stopDeadClickTracking();
19307
+ return;
19308
+ }
19309
+
19310
+ logger$1.log('Initializing dead click tracking');
19311
+ if (!this._deadClickTracker) {
19312
+ this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
19313
+ this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
19314
+ }.bind(this));
19315
+ this._deadClickTracker.startTracking();
19316
+ }
19317
+
19318
+ if (!this.listenerDeadClick) {
19319
+ this.listenerDeadClick = function(ev) {
19320
+ var currentDeadClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK);
19321
+ if (!currentDeadClickConfig && !this.mp.is_recording_heatmap_data()) {
19322
+ return;
19323
+ }
19324
+ if (this.currentUrlBlocked()) {
19325
+ return;
19326
+ }
19327
+ // Normalize config to ensure timeout_ms is always set
19328
+ var normalizedConfig = currentDeadClickConfig || {};
19329
+ if (!normalizedConfig['timeout_ms']) {
19330
+ normalizedConfig['timeout_ms'] = DEFAULT_DEAD_CLICK_TIMEOUT_MS;
19331
+ }
19332
+ this._deadClickTracker.trackClick(ev, normalizedConfig);
19333
+ }.bind(this);
19334
+ win.addEventListener(EV_CLICK, this.listenerDeadClick);
19335
+ }
18605
19336
  };
18606
19337
 
18607
19338
  Autocapture.prototype.initInputTracking = function() {
@@ -18612,17 +19343,16 @@ Autocapture.prototype.initInputTracking = function() {
18612
19343
  }
18613
19344
  logger$1.log('Initializing input tracking');
18614
19345
 
18615
- this.listenerChange = win.addEventListener(EV_CHANGE, function(ev) {
19346
+ this.listenerChange = function(ev) {
18616
19347
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
18617
19348
  return;
18618
19349
  }
18619
19350
  this.trackDomEvent(ev, MP_EV_INPUT);
18620
- }.bind(this));
19351
+ }.bind(this);
19352
+ win.addEventListener(EV_CHANGE, this.listenerChange);
18621
19353
  };
18622
19354
 
18623
19355
  Autocapture.prototype.initPageviewTracking = function() {
18624
- win.removeEventListener(EV_POPSTATE, this.listenerPopstate);
18625
- win.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
18626
19356
  win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
18627
19357
 
18628
19358
  if (!this.pageviewTrackingConfig()) {
@@ -18639,27 +19369,7 @@ Autocapture.prototype.initPageviewTracking = function() {
18639
19369
  previousTrackedUrl = _.info.currentUrl();
18640
19370
  }
18641
19371
 
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() {
19372
+ this.listenerLocationchange = safewrap(function() {
18663
19373
  if (this.currentUrlBlocked()) {
18664
19374
  return;
18665
19375
  }
@@ -18686,13 +19396,14 @@ Autocapture.prototype.initPageviewTracking = function() {
18686
19396
  logger$1.log('Path change: re-initializing scroll depth checkpoints');
18687
19397
  }
18688
19398
  }
18689
- }.bind(this)));
19399
+ }.bind(this));
19400
+ win.addEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
18690
19401
  };
18691
19402
 
18692
19403
  Autocapture.prototype.initRageClickTracking = function() {
18693
19404
  win.removeEventListener(EV_CLICK, this.listenerRageClick);
18694
19405
 
18695
- var rageClickConfig = this._getRageClickConfig();
19406
+ var rageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
18696
19407
  if (!rageClickConfig && !this.mp.get_config('record_heatmap_data')) {
18697
19408
  return;
18698
19409
  }
@@ -18703,7 +19414,7 @@ Autocapture.prototype.initRageClickTracking = function() {
18703
19414
  }
18704
19415
 
18705
19416
  this.listenerRageClick = function(ev) {
18706
- var currentRageClickConfig = this._getRageClickConfig();
19417
+ var currentRageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
18707
19418
  if (!currentRageClickConfig && !this.mp.is_recording_heatmap_data()) {
18708
19419
  return;
18709
19420
  }
@@ -18721,6 +19432,8 @@ Autocapture.prototype.initRageClickTracking = function() {
18721
19432
 
18722
19433
  Autocapture.prototype.initScrollTracking = function() {
18723
19434
  win.removeEventListener(EV_SCROLLEND, this.listenerScroll);
19435
+ win.removeEventListener(EV_SCROLL, this.listenerScroll);
19436
+
18724
19437
 
18725
19438
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
18726
19439
  return;
@@ -18728,7 +19441,7 @@ Autocapture.prototype.initScrollTracking = function() {
18728
19441
  logger$1.log('Initializing scroll tracking');
18729
19442
  this.lastScrollCheckpoint = 0;
18730
19443
 
18731
- this.listenerScroll = win.addEventListener(EV_SCROLLEND, safewrap(function() {
19444
+ var scrollTrackFunction = function() {
18732
19445
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
18733
19446
  return;
18734
19447
  }
@@ -18767,7 +19480,11 @@ Autocapture.prototype.initScrollTracking = function() {
18767
19480
  if (shouldTrack) {
18768
19481
  this.mp.track(MP_EV_SCROLL, props);
18769
19482
  }
18770
- }.bind(this)));
19483
+ }.bind(this);
19484
+
19485
+ var scrollEndPolyfill = getPolyfillScrollEndFunction(scrollTrackFunction);
19486
+ this.listenerScroll = scrollEndPolyfill.listener;
19487
+ win.addEventListener(scrollEndPolyfill.eventType, this.listenerScroll);
18771
19488
  };
18772
19489
 
18773
19490
  Autocapture.prototype.initSubmitTracking = function() {
@@ -18778,18 +19495,81 @@ Autocapture.prototype.initSubmitTracking = function() {
18778
19495
  }
18779
19496
  logger$1.log('Initializing submit tracking');
18780
19497
 
18781
- this.listenerSubmit = win.addEventListener(EV_SUBMIT, function(ev) {
19498
+ this.listenerSubmit = function(ev) {
18782
19499
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
18783
19500
  return;
18784
19501
  }
18785
19502
  this.trackDomEvent(ev, MP_EV_SUBMIT);
19503
+ }.bind(this);
19504
+ win.addEventListener(EV_SUBMIT, this.listenerSubmit);
19505
+ };
19506
+
19507
+ Autocapture.prototype.initPageLeaveTracking = function() {
19508
+ // Capture page_leave both when the user navigates away from the page (visibilitychange) as well
19509
+ // as when they navigate to a different page within the SPA (popstate/pushstate/hashchange).
19510
+ document$1.removeEventListener(EV_VISIBILITYCHANGE, this.listenerPageLeaveVisibilitychange);
19511
+ win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerPageLeaveLocationchange);
19512
+ win.removeEventListener(EV_LOAD, this.listenerPageLoad);
19513
+
19514
+ if (!this.getConfig(CONFIG_TRACK_PAGE_LEAVE) && !this.mp.get_config('record_heatmap_data')) {
19515
+ return;
19516
+ }
19517
+
19518
+ logger$1.log('Initializing page visibility tracking.');
19519
+ this._initScrollDepthTracking();
19520
+ var previousTrackedUrl = _.info.currentUrl();
19521
+
19522
+ // Initialize previousScrollHeight on `load` which handles async loading
19523
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event
19524
+ this.listenerPageLoad = function() {
19525
+ this.previousScrollHeight = document$1.body.scrollHeight;
19526
+ }.bind(this);
19527
+ win.addEventListener(EV_LOAD, this.listenerPageLoad);
19528
+
19529
+ // Track page navigation events similar to how initPageviewTracking does it
19530
+ this.listenerPageLeaveLocationchange = safewrap(function(ev) {
19531
+ if (this.currentUrlBlocked()) {
19532
+ return;
19533
+ }
19534
+
19535
+ var currentUrl = _.info.currentUrl();
19536
+ // Track all URL changes including query string or fragment changes as separate scroll sessions
19537
+ var shouldTrack = currentUrl !== previousTrackedUrl;
19538
+
19539
+ if (shouldTrack) {
19540
+ this._trackPageLeave(ev, previousTrackedUrl, this.previousScrollHeight);
19541
+ previousTrackedUrl = currentUrl;
19542
+ // Fragment navigation should call scroll(end) and trigger listener, don't add window.scrollY here.
19543
+ this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
19544
+ this.previousScrollHeight = document$1.body.scrollHeight;
19545
+ this.hasTrackedScrollSession = false;
19546
+ }
18786
19547
  }.bind(this));
19548
+ win.addEventListener(EV_MP_LOCATION_CHANGE, this.listenerPageLeaveLocationchange);
19549
+
19550
+ this.listenerPageLeaveVisibilitychange = function(ev) {
19551
+ if (document$1.hidden) {
19552
+ this._trackPageLeave(ev, previousTrackedUrl, this.previousScrollHeight);
19553
+ }
19554
+ }.bind(this);
19555
+ document$1.addEventListener(EV_VISIBILITYCHANGE, this.listenerPageLeaveVisibilitychange);
19556
+ };
19557
+
19558
+ Autocapture.prototype.stopDeadClickTracking = function() {
19559
+ if (this.listenerDeadClick) {
19560
+ win.removeEventListener(EV_CLICK, this.listenerDeadClick);
19561
+ this.listenerDeadClick = null;
19562
+ }
19563
+
19564
+ if (this._deadClickTracker) {
19565
+ this._deadClickTracker.stopTracking();
19566
+ this._deadClickTracker = null;
19567
+ }
18787
19568
  };
18788
19569
 
18789
19570
  // TODO integrate error_reporter from mixpanel instance
18790
19571
  safewrapClass(Autocapture);
18791
19572
 
18792
- var fetch = win['fetch'];
18793
19573
  var logger = console_with_prefix('flags');
18794
19574
 
18795
19575
  var FLAGS_CONFIG_KEY = 'flags';
@@ -18803,6 +19583,7 @@ CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
18803
19583
  * @constructor
18804
19584
  */
18805
19585
  var FeatureFlagManager = function(initOptions) {
19586
+ this.fetch = win['fetch'];
18806
19587
  this.getFullApiRoute = initOptions.getFullApiRoute;
18807
19588
  this.getMpConfig = initOptions.getConfigFunc;
18808
19589
  this.setMpConfig = initOptions.setConfigFunc;
@@ -18811,7 +19592,7 @@ var FeatureFlagManager = function(initOptions) {
18811
19592
  };
18812
19593
 
18813
19594
  FeatureFlagManager.prototype.init = function() {
18814
- if (!minApisSupported()) {
19595
+ if (!this.minApisSupported()) {
18815
19596
  logger.critical('Feature Flags unavailable: missing minimum required APIs');
18816
19597
  return;
18817
19598
  }
@@ -18874,6 +19655,7 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18874
19655
 
18875
19656
  var distinctId = this.getMpProperty('distinct_id');
18876
19657
  var deviceId = this.getMpProperty('$device_id');
19658
+ var traceparent = generateTraceparent();
18877
19659
  logger.log('Fetching flags for distinct ID: ' + distinctId);
18878
19660
 
18879
19661
  var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
@@ -18885,10 +19667,11 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18885
19667
  var url = this.getFullApiRoute() + '?' + searchParams.toString();
18886
19668
 
18887
19669
  this._fetchInProgressStartTime = Date.now();
18888
- this.fetchPromise = win['fetch'](url, {
19670
+ this.fetchPromise = this.fetch.call(win, url, {
18889
19671
  'method': 'GET',
18890
19672
  'headers': {
18891
- 'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':')
19673
+ 'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
19674
+ 'traceparent': traceparent
18892
19675
  }
18893
19676
  }).then(function(response) {
18894
19677
  this.markFetchComplete();
@@ -18901,10 +19684,14 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18901
19684
  _.each(responseFlags, function(data, key) {
18902
19685
  flags.set(key, {
18903
19686
  'key': data['variant_key'],
18904
- 'value': data['variant_value']
19687
+ 'value': data['variant_value'],
19688
+ 'experiment_id': data['experiment_id'],
19689
+ 'is_experiment_active': data['is_experiment_active'],
19690
+ 'is_qa_tester': data['is_qa_tester']
18905
19691
  });
18906
19692
  });
18907
19693
  this.flags = flags;
19694
+ this._traceparent = traceparent;
18908
19695
  }.bind(this)).catch(function(error) {
18909
19696
  this.markFetchComplete();
18910
19697
  logger.error(error);
@@ -19001,22 +19788,36 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
19001
19788
  return;
19002
19789
  }
19003
19790
  this.trackedFeatures.add(featureName);
19004
- this.track('$experiment_started', {
19791
+
19792
+ var trackingProperties = {
19005
19793
  'Experiment name': featureName,
19006
19794
  'Variant name': feature['key'],
19007
19795
  '$experiment_type': 'feature_flag',
19008
19796
  'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
19009
19797
  'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
19010
- 'Variant fetch latency (ms)': this._fetchLatency
19011
- });
19798
+ 'Variant fetch latency (ms)': this._fetchLatency,
19799
+ 'Variant fetch traceparent': this._traceparent,
19800
+ };
19801
+
19802
+ if (feature['experiment_id'] !== 'undefined') {
19803
+ trackingProperties['$experiment_id'] = feature['experiment_id'];
19804
+ }
19805
+ if (feature['is_experiment_active'] !== 'undefined') {
19806
+ trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
19807
+ }
19808
+ if (feature['is_qa_tester'] !== 'undefined') {
19809
+ trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
19810
+ }
19811
+
19812
+ this.track('$experiment_started', trackingProperties);
19012
19813
  };
19013
19814
 
19014
- function minApisSupported() {
19015
- return !!fetch &&
19815
+ FeatureFlagManager.prototype.minApisSupported = function() {
19816
+ return !!this.fetch &&
19016
19817
  typeof Promise !== 'undefined' &&
19017
19818
  typeof Map !== 'undefined' &&
19018
19819
  typeof Set !== 'undefined';
19019
- }
19820
+ };
19020
19821
 
19021
19822
  safewrapClass(FeatureFlagManager);
19022
19823