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