mixpanel-browser 2.70.0 → 2.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/build.sh +1 -0
  3. package/dist/mixpanel-core.cjs.d.ts +440 -0
  4. package/dist/mixpanel-core.cjs.js +847 -50
  5. package/dist/mixpanel-recorder.js +5 -3
  6. package/dist/mixpanel-recorder.min.js +1 -1
  7. package/dist/mixpanel-recorder.min.js.map +1 -1
  8. package/dist/mixpanel-with-async-recorder.cjs.d.ts +440 -0
  9. package/dist/mixpanel-with-async-recorder.cjs.js +847 -50
  10. package/dist/mixpanel-with-recorder.d.ts +440 -0
  11. package/dist/mixpanel-with-recorder.js +851 -52
  12. package/dist/mixpanel-with-recorder.min.d.ts +440 -0
  13. package/dist/mixpanel-with-recorder.min.js +1 -1
  14. package/dist/mixpanel.amd.d.ts +440 -0
  15. package/dist/mixpanel.amd.js +851 -52
  16. package/dist/mixpanel.cjs.d.ts +440 -0
  17. package/dist/mixpanel.cjs.js +851 -52
  18. package/dist/mixpanel.globals.js +847 -50
  19. package/dist/mixpanel.min.js +170 -153
  20. package/dist/mixpanel.module.d.ts +440 -0
  21. package/dist/mixpanel.module.js +851 -52
  22. package/dist/mixpanel.umd.d.ts +440 -0
  23. package/dist/mixpanel.umd.js +851 -52
  24. package/dist/rrweb-compiled.js +4 -2
  25. package/package.json +2 -19
  26. package/rollup.config.mjs +28 -4
  27. package/src/autocapture/deadclick.js +254 -0
  28. package/src/autocapture/index.js +237 -41
  29. package/src/autocapture/shadow-dom-observer.js +100 -0
  30. package/src/autocapture/utils.js +230 -3
  31. package/src/config.js +1 -1
  32. package/src/flags/index.js +32 -12
  33. package/src/index.d.ts +16 -3
  34. package/src/loaders/loader-module-core.d.ts +1 -0
  35. package/src/loaders/loader-module-with-async-recorder.d.ts +1 -0
  36. package/src/loaders/loader-module.d.ts +1 -0
  37. package/src/utils.js +15 -0
@@ -3,7 +3,7 @@
3
3
 
4
4
  var Config = {
5
5
  DEBUG: false,
6
- LIB_VERSION: '2.70.0'
6
+ LIB_VERSION: '2.71.0'
7
7
  };
8
8
 
9
9
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
@@ -2065,6 +2065,20 @@
2065
2065
  return maxlen ? guid.substring(0, maxlen) : guid;
2066
2066
  };
2067
2067
 
2068
+ /**
2069
+ * Generates a W3C traceparent header for easy interop with distributed tracing systems i.e Open Telemetry
2070
+ * https://www.w3.org/TR/trace-context/#traceparent-header
2071
+ */
2072
+ var generateTraceparent = function() {
2073
+ var traceID = _.UUID().replace(/-/g, '');
2074
+ var parentID = _.UUID().replace(/-/g, '').substring(0, 16);
2075
+
2076
+ // Sampled trace
2077
+ var traceFlags = '01';
2078
+
2079
+ return '00-' + traceID + '-' + parentID + '-' + traceFlags;
2080
+ };
2081
+
2068
2082
  // naive way to extract domain name (example.com) from full hostname (my.sub.example.com)
2069
2083
  var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i;
2070
2084
  // this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk
@@ -2142,11 +2156,17 @@
2142
2156
  var EV_CHANGE = 'change';
2143
2157
  var EV_CLICK = 'click';
2144
2158
  var EV_HASHCHANGE = 'hashchange';
2159
+ var EV_INPUT = 'input';
2160
+ var EV_LOAD = 'load';
2145
2161
  var EV_MP_LOCATION_CHANGE = 'mp_locationchange';
2146
2162
  var EV_POPSTATE = 'popstate';
2147
2163
  // TODO scrollend isn't available in Safari: document or polyfill?
2148
2164
  var EV_SCROLLEND = 'scrollend';
2165
+ var EV_SCROLL = 'scroll';
2166
+ var EV_SELECT = 'select';
2149
2167
  var EV_SUBMIT = 'submit';
2168
+ var EV_TOGGLE = 'toggle';
2169
+ var EV_VISIBILITYCHANGE = 'visibilitychange';
2150
2170
 
2151
2171
  var CLICK_EVENT_PROPS = [
2152
2172
  'clientX', 'clientY',
@@ -2163,6 +2183,77 @@
2163
2183
  'href', 'name', 'role', 'title', 'type'
2164
2184
  ];
2165
2185
 
2186
+ var INTERACTIVE_ARIA_ROLES = {
2187
+ 'button': true,
2188
+ 'checkbox': true,
2189
+ 'combobox': true,
2190
+ 'grid': true,
2191
+ 'link': true,
2192
+ 'listbox': true,
2193
+ 'menu': true,
2194
+ 'menubar': true,
2195
+ 'menuitem': true,
2196
+ 'menuitemcheckbox': true,
2197
+ 'menuitemradio': true,
2198
+ 'navigation': true,
2199
+ 'option': true,
2200
+ 'radio': true,
2201
+ 'radiogroup': true,
2202
+ 'searchbox': true,
2203
+ 'slider': true,
2204
+ 'spinbutton': true,
2205
+ 'switch': true,
2206
+ 'tab': true,
2207
+ 'tablist': true,
2208
+ 'textbox': true,
2209
+ 'tree': true,
2210
+ 'treegrid': true,
2211
+ 'treeitem': true
2212
+ };
2213
+
2214
+ var ALWAYS_NON_INTERACTIVE_TAGS = {
2215
+ // Document metadata
2216
+ 'base': true,
2217
+ 'head': true,
2218
+ 'html': true,
2219
+ 'link': true,
2220
+ 'meta': true,
2221
+ 'script': true,
2222
+ 'style': true,
2223
+ 'title': true,
2224
+ // Text formatting
2225
+ 'br': true,
2226
+ 'hr': true,
2227
+ 'wbr': true,
2228
+ // Other
2229
+ 'noscript': true,
2230
+ 'picture': true,
2231
+ 'source': true,
2232
+ 'template': true,
2233
+ 'track': true
2234
+ };
2235
+
2236
+ // Common container tags that need additional checks
2237
+ var TEXT_CONTAINER_TAGS = {
2238
+ 'article': true,
2239
+ 'div': true,
2240
+ 'h1': true,
2241
+ 'h2': true,
2242
+ 'h3': true,
2243
+ 'h4': true,
2244
+ 'h5': true,
2245
+ 'h6': true,
2246
+ 'p': true,
2247
+ 'section': true,
2248
+ 'span': true
2249
+ };
2250
+
2251
+ var EVENT_HANDLER_ATTRIBUTES = [
2252
+ 'onclick', 'onmousedown', 'onmouseup', 'onpointerdown', 'onpointerup', 'ontouchend', 'ontouchstart'
2253
+ ];
2254
+
2255
+ var MAX_DEPTH = 5;
2256
+
2166
2257
  var logger$4 = console_with_prefix('autocapture');
2167
2258
 
2168
2259
 
@@ -2530,6 +2621,10 @@
2530
2621
  }
2531
2622
  }
2532
2623
 
2624
+ function weakSetSupported() {
2625
+ return typeof WeakSet !== 'undefined';
2626
+ }
2627
+
2533
2628
  /*
2534
2629
  * Check whether a DOM event should be "tracked" or if it may contain sensitive data
2535
2630
  * using a variety of heuristics.
@@ -2655,6 +2750,149 @@
2655
2750
  return true;
2656
2751
  }
2657
2752
 
2753
+ /**
2754
+ * Creates a cross-browser compatible scroll end function with appropriate event listener.
2755
+ * For browsers that support scrollend, returns the original function with scrollend event.
2756
+ * For browsers without scrollend support, returns a debounced function that triggers
2757
+ * 100ms after the last scroll event to simulate scrollend behavior.
2758
+ * @param {Function} originalFunction - The function to call when scrolling ends
2759
+ * @returns {Object} Object containing listener function and eventType string
2760
+ * @returns {Function} returns.listener - The wrapped function to use as event listener
2761
+ * @returns {string} returns.eventType - The event type to listen for ('scrollend' or 'scroll')
2762
+ */
2763
+ function getPolyfillScrollEndFunction(originalFunction) {
2764
+ var supportsScrollEnd = 'onscrollend' in win;
2765
+ var polyfillFunction = safewrap(originalFunction);
2766
+ var polyfillEvent = EV_SCROLLEND;
2767
+ if (!supportsScrollEnd) {
2768
+ // Polyfill for browsers without scrollend support: wait 100ms after the last scroll event
2769
+ // https://developer.chrome.com/blog/scrollend-a-new-javascript-event
2770
+ var scrollTimer = null;
2771
+ var scrollDelayMs = 100;
2772
+
2773
+ polyfillFunction = safewrap(function() {
2774
+ clearTimeout(scrollTimer);
2775
+ scrollTimer = setTimeout(originalFunction, scrollDelayMs);
2776
+ });
2777
+
2778
+ polyfillEvent = EV_SCROLL;
2779
+ }
2780
+
2781
+ return {
2782
+ listener: polyfillFunction,
2783
+ eventType: polyfillEvent
2784
+ };
2785
+ }
2786
+
2787
+ function hasInlineEventHandlers(element) {
2788
+ for (var i = 0; i < EVENT_HANDLER_ATTRIBUTES.length; i++) {
2789
+ if (element.hasAttribute(EVENT_HANDLER_ATTRIBUTES[i])) {
2790
+ return true;
2791
+ }
2792
+ }
2793
+ return false;
2794
+ }
2795
+
2796
+ function hasInteractiveAriaRole(element) {
2797
+ var role = element.getAttribute('role');
2798
+ if (!role) return false;
2799
+
2800
+ // Handle invalid markup where multiple roles might be specified
2801
+ // Only the first token is recognized per ARIA spec
2802
+ var primaryRole = role.trim().split(/\s+/)[0].toLowerCase();
2803
+
2804
+ return INTERACTIVE_ARIA_ROLES[primaryRole];
2805
+ }
2806
+
2807
+ function hasAnyInteractivityIndicators(element) {
2808
+ var tagName = element.tagName.toLowerCase();
2809
+
2810
+ // Check for interactive HTML elements
2811
+ if (tagName === 'button' ||
2812
+ tagName === 'input' ||
2813
+ tagName === 'select' ||
2814
+ tagName === 'textarea' ||
2815
+ tagName === 'details' ||
2816
+ tagName === 'dialog') {
2817
+ return true;
2818
+ }
2819
+
2820
+ if (element.isContentEditable) {
2821
+ return true;
2822
+ }
2823
+
2824
+ if (element.onclick || element.onmousedown || element.onmouseup || element.ontouchstart || element.ontouchend) {
2825
+ return true;
2826
+ }
2827
+
2828
+ if (hasInlineEventHandlers(element)) {
2829
+ return true;
2830
+ }
2831
+
2832
+ if (hasInteractiveAriaRole(element)) {
2833
+ return true;
2834
+ }
2835
+
2836
+ if (tagName === 'a' && element.hasAttribute('href')) {
2837
+ return true;
2838
+ }
2839
+
2840
+ if (element.hasAttribute('tabindex')) {
2841
+ return true;
2842
+ }
2843
+
2844
+ return false;
2845
+ }
2846
+
2847
+
2848
+ function isDefinitelyNonInteractive(element) {
2849
+ if (!element || !element.tagName) {
2850
+ return true;
2851
+ }
2852
+
2853
+ var tagName = element.tagName.toLowerCase();
2854
+
2855
+ // These tags are definitely non-interactive
2856
+ if (ALWAYS_NON_INTERACTIVE_TAGS[tagName]) {
2857
+ return true;
2858
+ }
2859
+
2860
+ // For all other elements, we can only be certain they're non-interactive if they lack ALL indicators of interactivity
2861
+ // Check for any signs of interactivity
2862
+ if (hasAnyInteractivityIndicators(element)) {
2863
+ return false;
2864
+ }
2865
+
2866
+ // Check parent chain for interactive context
2867
+ var parent = element.parentElement;
2868
+ var depth = 0;
2869
+
2870
+ while (parent && depth < MAX_DEPTH) {
2871
+ if (hasAnyInteractivityIndicators(parent)) {
2872
+ return false; // Element is inside an interactive parent
2873
+ }
2874
+
2875
+ if (parent.getRootNode && parent.getRootNode() !== document$1) {
2876
+ var root = parent.getRootNode();
2877
+ if (root.host && hasAnyInteractivityIndicators(root.host)) {
2878
+ return false; // Inside an interactive shadow host
2879
+ }
2880
+ }
2881
+
2882
+ parent = parent.parentElement;
2883
+ depth++;
2884
+ }
2885
+
2886
+ // Pure text containers without any interactive context
2887
+ if (TEXT_CONTAINER_TAGS[tagName]) {
2888
+ // These are non-interactive ONLY if they have no interactive indicators (already checked as part of hasAnyInteractivityIndicators)
2889
+ return true;
2890
+ }
2891
+
2892
+ // Default: we can't be certain it's non-interactive
2893
+ return false;
2894
+ }
2895
+
2658
2896
  /** @const */ var DEFAULT_RAGE_CLICK_THRESHOLD_PX = 30;
2659
2897
  /** @const */ var DEFAULT_RAGE_CLICK_TIMEOUT_MS = 1000;
2660
2898
  /** @const */ var DEFAULT_RAGE_CLICK_CLICK_COUNT = 4;
@@ -2687,6 +2925,350 @@
2687
2925
  return false;
2688
2926
  };
2689
2927
 
2928
+ function ShadowDOMObserver(changeCallback, observerConfig) {
2929
+ this.changeCallback = changeCallback || function() {};
2930
+ this.observerConfig = observerConfig;
2931
+
2932
+ this.observedShadowRoots = null;
2933
+ this.shadowObservers = [];
2934
+ }
2935
+
2936
+ ShadowDOMObserver.prototype.getEventTarget = function(event) {
2937
+ if (!this.observedShadowRoots) {
2938
+ return;
2939
+ }
2940
+ var path = this.getComposedPath(event);
2941
+ if (path && path.length) {
2942
+ return path[0];
2943
+ }
2944
+
2945
+ return event['target'] || event['srcElement'];
2946
+ };
2947
+
2948
+
2949
+ ShadowDOMObserver.prototype.getComposedPath = function(event) {
2950
+ if ('composedPath' in event) {
2951
+ return event['composedPath']();
2952
+ }
2953
+
2954
+ return [];
2955
+ };
2956
+ ShadowDOMObserver.prototype.observeFromEvent = function(event) {
2957
+ if (!this.observedShadowRoots) {
2958
+ return;
2959
+ }
2960
+
2961
+ var path = this.getComposedPath(event);
2962
+
2963
+ // Check each element in path for shadow roots
2964
+ for (var i = 0; i < path.length; i++) {
2965
+ var element = path[i];
2966
+
2967
+ if (element && element.shadowRoot) {
2968
+ this.observeShadowRoot(element.shadowRoot);
2969
+ }
2970
+ }
2971
+ };
2972
+
2973
+
2974
+ ShadowDOMObserver.prototype.observeShadowRoot = function(shadowRoot) {
2975
+ if (!this.observedShadowRoots || this.observedShadowRoots.has(shadowRoot)) {
2976
+ return;
2977
+ }
2978
+
2979
+ var self = this;
2980
+
2981
+ try {
2982
+ this.observedShadowRoots.add(shadowRoot);
2983
+
2984
+ var observer = new window.MutationObserver(function() {
2985
+ self.changeCallback();
2986
+ });
2987
+
2988
+ observer.observe(shadowRoot, this.observerConfig);
2989
+ this.shadowObservers.push(observer);
2990
+ } catch (e) {
2991
+ logger$4.critical('Error while observing shadow root', e);
2992
+ }
2993
+ };
2994
+
2995
+
2996
+ ShadowDOMObserver.prototype.start = function() {
2997
+ if (this.observedShadowRoots) {
2998
+ return;
2999
+ }
3000
+
3001
+ if (!weakSetSupported()) {
3002
+ logger$4.critical('Shadow DOM observation unavailable: WeakSet not supported');
3003
+ return;
3004
+ }
3005
+
3006
+ this.observedShadowRoots = new WeakSet();
3007
+ };
3008
+
3009
+ ShadowDOMObserver.prototype.stop = function() {
3010
+ if (!this.observedShadowRoots) {
3011
+ return;
3012
+ }
3013
+
3014
+ for (var i = 0; i < this.shadowObservers.length; i++) {
3015
+ try {
3016
+ this.shadowObservers[i].disconnect();
3017
+ } catch (e) {
3018
+ logger$4.critical('Error while disconnecting shadow DOM observer', e);
3019
+ }
3020
+ }
3021
+ this.shadowObservers = [];
3022
+ this.observedShadowRoots = null;
3023
+ };
3024
+
3025
+ /** @const */ var DEFAULT_DEAD_CLICK_TIMEOUT_MS = 500;
3026
+ /** @const */ var INTERACTION_EVENTS = [EV_CHANGE, EV_INPUT, EV_SUBMIT, EV_SELECT, EV_TOGGLE];
3027
+ /** @const */ var LAYOUT_EVENTS = [EV_SCROLLEND];
3028
+ /** @const */ var NAVIGATION_EVENTS = [EV_HASHCHANGE];
3029
+ /** @const */ var MUTATION_OBSERVER_CONFIG = {
3030
+ characterData: true,
3031
+ childList: true,
3032
+ subtree: true,
3033
+ attributes: true,
3034
+ attributeFilter: ['style', 'class', 'hidden', 'checked', 'selected', 'value', 'display', 'visibility']
3035
+ };
3036
+
3037
+
3038
+ function DeadClickTracker(onDeadClickCallback) {
3039
+ this.eventListeners = [];
3040
+ this.mutationObserver = null;
3041
+ this.shadowDOMObserver = null;
3042
+
3043
+ this.isTracking = false;
3044
+ this.lastChangeEventTimestamp = 0;
3045
+ this.pendingClicks = [];
3046
+ this.onDeadClickCallback = onDeadClickCallback;
3047
+ this.processingActive = false;
3048
+ this.processingTimeout = null;
3049
+ }
3050
+
3051
+
3052
+ DeadClickTracker.prototype.addClick = function(event) {
3053
+ var element = this.shadowDOMObserver && this.shadowDOMObserver.getEventTarget(event);
3054
+
3055
+ if (!element) {
3056
+ element = event['target'] || event['srcElement'];
3057
+ }
3058
+
3059
+ if (!element || isDefinitelyNonInteractive(element)) {
3060
+ return false;
3061
+ }
3062
+
3063
+ if (this.shadowDOMObserver) {
3064
+ this.shadowDOMObserver.observeFromEvent(event);
3065
+ }
3066
+ this.pendingClicks.push({
3067
+ element: element,
3068
+ event: event,
3069
+ timestamp: Date.now()
3070
+ });
3071
+ return true;
3072
+ };
3073
+
3074
+ DeadClickTracker.prototype.trackClick = function(event, config) {
3075
+ if (!this.isTracking) {
3076
+ return false;
3077
+ }
3078
+
3079
+ var added = this.addClick(event);
3080
+ if (added) {
3081
+ this.triggerProcessing(config);
3082
+ }
3083
+ return added;
3084
+ };
3085
+
3086
+ DeadClickTracker.prototype.getDeadClicks = function(config) {
3087
+ if (this.pendingClicks.length === 0) {
3088
+ return [];
3089
+ }
3090
+
3091
+ var timeoutMs = config['timeout_ms'];
3092
+ var now = Date.now();
3093
+ var clicksToEvaluate = this.pendingClicks.slice(); // Copy array
3094
+ this.pendingClicks = []; // Clear original
3095
+
3096
+ var deadClicks = [];
3097
+
3098
+ for (var i = 0; i < clicksToEvaluate.length; i++) {
3099
+ var click = clicksToEvaluate[i];
3100
+
3101
+ if (now - click.timestamp >= timeoutMs) {
3102
+ // Click has exceeded timeout, check if it's dead by looking for changes after this specific click
3103
+ if (!this.hasChangesAfter(click.timestamp)) {
3104
+ deadClicks.push(click);
3105
+ }
3106
+ } else {
3107
+ // Still pending - add back
3108
+ this.pendingClicks.push(click);
3109
+ }
3110
+ }
3111
+
3112
+ return deadClicks;
3113
+ };
3114
+
3115
+ DeadClickTracker.prototype.hasChangesAfter = function(timestamp) {
3116
+ // 100ms tolerance for race condition between when we record the click and the change event
3117
+ return this.lastChangeEventTimestamp >= (timestamp - 100);
3118
+ };
3119
+
3120
+ DeadClickTracker.prototype.recordChangeEvent = function() {
3121
+ this.lastChangeEventTimestamp = Date.now();
3122
+ };
3123
+
3124
+ DeadClickTracker.prototype.triggerProcessing = function(config) {
3125
+ // Prevent multiple concurrent processing chains
3126
+ if (this.processingActive) {
3127
+ return;
3128
+ }
3129
+ this.processingActive = true;
3130
+ this.processRecursively(config);
3131
+ };
3132
+
3133
+ DeadClickTracker.prototype.processRecursively = function(config) {
3134
+ if (!this.isTracking || !this.onDeadClickCallback) {
3135
+ this.processingActive = false;
3136
+ return;
3137
+ }
3138
+
3139
+ var timeoutMs = config['timeout_ms'];
3140
+ var self = this;
3141
+
3142
+ this.processingTimeout = setTimeout(function() {
3143
+ if (!self.processingActive) {
3144
+ return;
3145
+ }
3146
+
3147
+ var deadClicks = self.getDeadClicks(config);
3148
+
3149
+ for (var i = 0; i < deadClicks.length; i++) {
3150
+ self.onDeadClickCallback(deadClicks[i].event);
3151
+ }
3152
+
3153
+ if (self.pendingClicks.length > 0) {
3154
+ self.processRecursively(config);
3155
+ } else {
3156
+ self.processingActive = false;
3157
+ }
3158
+ }, timeoutMs);
3159
+ };
3160
+
3161
+ DeadClickTracker.prototype.startTracking = function() {
3162
+ if (this.isTracking) {
3163
+ return;
3164
+ }
3165
+
3166
+ this.isTracking = true;
3167
+
3168
+ var self = this;
3169
+
3170
+ INTERACTION_EVENTS.forEach(function(event) {
3171
+ var handler = function() {
3172
+ self.recordChangeEvent();
3173
+ };
3174
+ document.addEventListener(event, handler, { capture: true, passive: true });
3175
+ self.eventListeners.push({ target: document, event: event, handler: handler, options: { capture: true, passive: true } });
3176
+ });
3177
+ NAVIGATION_EVENTS.forEach(function(event) {
3178
+ var handler = function() {
3179
+ self.recordChangeEvent();
3180
+ };
3181
+ window.addEventListener(event, handler);
3182
+ self.eventListeners.push({ target: window, event: event, handler: handler });
3183
+ });
3184
+ LAYOUT_EVENTS.forEach(function(event) {
3185
+ var handler = function() {
3186
+ self.recordChangeEvent();
3187
+ };
3188
+ window.addEventListener(event, handler, { passive: true });
3189
+ self.eventListeners.push({ target: window, event: event, handler: handler, options: { passive: true } });
3190
+ });
3191
+ var selectionHandler = function() {
3192
+ self.recordChangeEvent();
3193
+ };
3194
+ document.addEventListener('selectionchange', selectionHandler);
3195
+ self.eventListeners.push({ target: document, event: 'selectionchange', handler: selectionHandler });
3196
+
3197
+ // Set up MutationObserver
3198
+ if (window.MutationObserver) {
3199
+ try {
3200
+ this.mutationObserver = new window.MutationObserver(function() {
3201
+ self.recordChangeEvent();
3202
+ });
3203
+
3204
+ this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
3205
+ } catch (e) {
3206
+ logger$4.critical('Error while setting up mutation observer', e);
3207
+ }
3208
+ }
3209
+
3210
+ // Set up Shadow DOM observer
3211
+ if (window.customElements) {
3212
+ try {
3213
+ this.shadowDOMObserver = new ShadowDOMObserver(
3214
+ function() {
3215
+ self.recordChangeEvent();
3216
+ },
3217
+ MUTATION_OBSERVER_CONFIG
3218
+ );
3219
+ this.shadowDOMObserver.start();
3220
+ } catch (e) {
3221
+ logger$4.critical('Error while setting up shadow DOM observer', e);
3222
+ this.shadowDOMObserver = null;
3223
+ }
3224
+ }
3225
+ };
3226
+
3227
+ DeadClickTracker.prototype.stopTracking = function() {
3228
+ if (!this.isTracking) {
3229
+ return;
3230
+ }
3231
+
3232
+ this.isTracking = false;
3233
+ this.pendingClicks = [];
3234
+ this.lastChangeEventTimestamp = 0;
3235
+ this.processingActive = false;
3236
+
3237
+ if (this.processingTimeout) {
3238
+ clearTimeout(this.processingTimeout);
3239
+ this.processingTimeout = null;
3240
+ }
3241
+
3242
+ // Remove all event listeners
3243
+ for (var i = 0; i < this.eventListeners.length; i++) {
3244
+ var listener = this.eventListeners[i];
3245
+ try {
3246
+ listener.target.removeEventListener(listener.event, listener.handler, listener.options);
3247
+ } catch (e) {
3248
+ logger$4.critical('Error while removing event listener', e);
3249
+ }
3250
+ }
3251
+ this.eventListeners = [];
3252
+
3253
+ if (this.mutationObserver) {
3254
+ try {
3255
+ this.mutationObserver.disconnect();
3256
+ } catch (e) {
3257
+ logger$4.critical('Error while disconnecting mutation observer', e);
3258
+ }
3259
+ this.mutationObserver = null;
3260
+ }
3261
+
3262
+ if (this.shadowDOMObserver) {
3263
+ try {
3264
+ this.shadowDOMObserver.stop();
3265
+ } catch (e) {
3266
+ logger$4.critical('Error while stopping shadow DOM observer', e);
3267
+ }
3268
+ this.shadowDOMObserver = null;
3269
+ }
3270
+ };
3271
+
2690
3272
  var AUTOCAPTURE_CONFIG_KEY = 'autocapture';
2691
3273
  var LEGACY_PAGEVIEW_CONFIG_KEY = 'track_pageview';
2692
3274
 
@@ -2706,10 +3288,12 @@
2706
3288
  var CONFIG_SCROLL_CAPTURE_ALL = 'scroll_capture_all';
2707
3289
  var CONFIG_SCROLL_CHECKPOINTS = 'scroll_depth_percent_checkpoints';
2708
3290
  var CONFIG_TRACK_CLICK = 'click';
3291
+ var CONFIG_TRACK_DEAD_CLICK = 'dead_click';
2709
3292
  var CONFIG_TRACK_INPUT = 'input';
2710
3293
  var CONFIG_TRACK_PAGEVIEW = 'pageview';
2711
3294
  var CONFIG_TRACK_RAGE_CLICK = 'rage_click';
2712
3295
  var CONFIG_TRACK_SCROLL = 'scroll';
3296
+ var CONFIG_TRACK_PAGE_LEAVE = 'page_leave';
2713
3297
  var CONFIG_TRACK_SUBMIT = 'submit';
2714
3298
 
2715
3299
  var CONFIG_DEFAULTS$1 = {};
@@ -2724,10 +3308,12 @@
2724
3308
  CONFIG_DEFAULTS$1[CONFIG_SCROLL_CAPTURE_ALL] = false;
2725
3309
  CONFIG_DEFAULTS$1[CONFIG_SCROLL_CHECKPOINTS] = [25, 50, 75, 100];
2726
3310
  CONFIG_DEFAULTS$1[CONFIG_TRACK_CLICK] = true;
3311
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_DEAD_CLICK] = true;
2727
3312
  CONFIG_DEFAULTS$1[CONFIG_TRACK_INPUT] = true;
2728
3313
  CONFIG_DEFAULTS$1[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
2729
3314
  CONFIG_DEFAULTS$1[CONFIG_TRACK_RAGE_CLICK] = true;
2730
3315
  CONFIG_DEFAULTS$1[CONFIG_TRACK_SCROLL] = true;
3316
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_PAGE_LEAVE] = false;
2731
3317
  CONFIG_DEFAULTS$1[CONFIG_TRACK_SUBMIT] = true;
2732
3318
 
2733
3319
  var DEFAULT_PROPS = {
@@ -2735,10 +3321,12 @@
2735
3321
  };
2736
3322
 
2737
3323
  var MP_EV_CLICK = '$mp_click';
3324
+ var MP_EV_DEAD_CLICK = '$mp_dead_click';
2738
3325
  var MP_EV_INPUT = '$mp_input_change';
2739
3326
  var MP_EV_RAGE_CLICK = '$mp_rage_click';
2740
3327
  var MP_EV_SCROLL = '$mp_scroll';
2741
3328
  var MP_EV_SUBMIT = '$mp_submit';
3329
+ var MP_EV_PAGE_LEAVE = '$mp_page_leave';
2742
3330
 
2743
3331
  /**
2744
3332
  * Autocapture: manages automatic event tracking
@@ -2746,6 +3334,9 @@
2746
3334
  */
2747
3335
  var Autocapture = function(mp) {
2748
3336
  this.mp = mp;
3337
+ this.maxScrollViewDepth = 0;
3338
+ this.hasTrackedScrollSession = false;
3339
+ this.previousScrollHeight = 0;
2749
3340
  };
2750
3341
 
2751
3342
  Autocapture.prototype.init = function() {
@@ -2753,13 +3344,15 @@
2753
3344
  logger$4.critical('Autocapture unavailable: missing required DOM APIs');
2754
3345
  return;
2755
3346
  }
2756
-
3347
+ this.initPageListeners();
2757
3348
  this.initPageviewTracking();
2758
3349
  this.initClickTracking();
3350
+ this.initDeadClickTracking();
2759
3351
  this.initInputTracking();
2760
3352
  this.initScrollTracking();
2761
3353
  this.initSubmitTracking();
2762
3354
  this.initRageClickTracking();
3355
+ this.initPageLeaveTracking();
2763
3356
  };
2764
3357
 
2765
3358
  Autocapture.prototype.getFullConfig = function() {
@@ -2840,7 +3433,8 @@
2840
3433
 
2841
3434
  var isCapturedForHeatMap = this.mp.is_recording_heatmap_data() && (
2842
3435
  (mpEventName === MP_EV_CLICK && !this.getConfig(CONFIG_TRACK_CLICK)) ||
2843
- (mpEventName === MP_EV_RAGE_CLICK && !this._getRageClickConfig())
3436
+ (mpEventName === MP_EV_RAGE_CLICK && !this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK)) ||
3437
+ (mpEventName === MP_EV_DEAD_CLICK && !this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK))
2844
3438
  );
2845
3439
 
2846
3440
  var props = getPropsForDOMEvent(ev, {
@@ -2859,11 +3453,45 @@
2859
3453
  }
2860
3454
  };
2861
3455
 
2862
- Autocapture.prototype._getRageClickConfig = function() {
2863
- var config = this.getConfig(CONFIG_TRACK_RAGE_CLICK);
3456
+ Autocapture.prototype.initPageListeners = function() {
3457
+ win.removeEventListener(EV_POPSTATE, this.listenerPopstate);
3458
+ win.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
3459
+
3460
+ if (!this.pageviewTrackingConfig() && !this.getConfig(CONFIG_TRACK_PAGE_LEAVE) && !this.mp.get_config('record_heatmap_data')) {
3461
+ // These are all the configs that use these listeners
3462
+ return;
3463
+ }
3464
+
3465
+ this.listenerPopstate = function() {
3466
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
3467
+ };
3468
+ this.listenerHashchange = function() {
3469
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
3470
+ };
3471
+
3472
+ win.addEventListener(EV_POPSTATE, this.listenerPopstate);
3473
+ win.addEventListener(EV_HASHCHANGE, this.listenerHashchange);
3474
+ var nativePushState = win.history.pushState;
3475
+ if (typeof nativePushState === 'function') {
3476
+ win.history.pushState = function(state, unused, url) {
3477
+ nativePushState.call(win.history, state, unused, url);
3478
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
3479
+ };
3480
+ }
3481
+ var nativeReplaceState = win.history.replaceState;
3482
+ if (typeof nativeReplaceState === 'function') {
3483
+ win.history.replaceState = function(state, unused, url) {
3484
+ nativeReplaceState.call(win.history, state, unused, url);
3485
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
3486
+ };
3487
+ }
3488
+ };
3489
+
3490
+ Autocapture.prototype._getClickTrackingConfig = function(configKey) {
3491
+ var config = this.getConfig(configKey);
2864
3492
 
2865
3493
  if (!config) {
2866
- return null; // rage click tracking disabled
3494
+ return null; // click tracking disabled
2867
3495
  }
2868
3496
 
2869
3497
  if (config === true) {
@@ -2877,6 +3505,68 @@
2877
3505
  return {}; // fallback to defaults for any other truthy value
2878
3506
  };
2879
3507
 
3508
+ Autocapture.prototype._trackPageLeave = function(ev, currentUrl, currentScrollHeight) {
3509
+ if (this.hasTrackedScrollSession) {
3510
+ // User has navigated away already ending their impression.
3511
+ return;
3512
+ }
3513
+ this.hasTrackedScrollSession = true;
3514
+ var viewportHeight = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
3515
+ var scrollPercentage = Math.round(Math.max(this.maxScrollViewDepth - viewportHeight, 0) / (currentScrollHeight - viewportHeight) * 100);
3516
+ var foldLinePercentage = Math.round((viewportHeight / currentScrollHeight) * 100);
3517
+ if (currentScrollHeight <= viewportHeight) {
3518
+ // If the content fits within the viewport, consider it fully scrolled
3519
+ scrollPercentage = 100;
3520
+ foldLinePercentage = 100;
3521
+ }
3522
+
3523
+ var props = _.extend({
3524
+ '$max_scroll_view_depth': this.maxScrollViewDepth,
3525
+ '$max_scroll_percentage': scrollPercentage,
3526
+ '$fold_line_percentage': foldLinePercentage,
3527
+ '$scroll_height': currentScrollHeight,
3528
+ '$event_type': ev.type,
3529
+ '$current_url': currentUrl || _.info.currentUrl(),
3530
+ '$viewportHeight': viewportHeight, // This is the fold line
3531
+ '$viewportWidth': Math.max(document$1.documentElement.clientWidth, win.innerWidth || 0),
3532
+ }, DEFAULT_PROPS);
3533
+
3534
+ if (this.mp.is_recording_heatmap_data() && !this.getConfig(CONFIG_TRACK_PAGE_LEAVE)) {
3535
+ props['$captured_for_heatmap'] = true;
3536
+ }
3537
+
3538
+ // Send with beacon transport to ensure event is sent before unload
3539
+ this.mp.track(MP_EV_PAGE_LEAVE, props, {transport: 'sendBeacon'});
3540
+ };
3541
+
3542
+ Autocapture.prototype._initScrollDepthTracking = function() {
3543
+ win.removeEventListener(EV_SCROLL, this.listenerScrollDepth);
3544
+ win.removeEventListener(EV_SCROLLEND, this.listenerScrollDepth);
3545
+
3546
+ if (!this.mp.get_config('record_heatmap_data')) {
3547
+ return;
3548
+ }
3549
+
3550
+ logger$4.log('Initializing scroll depth tracking');
3551
+
3552
+ this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
3553
+
3554
+ var updateScrollDepth = function() {
3555
+ if (this.currentUrlBlocked()) {
3556
+ return;
3557
+ }
3558
+ var scrollViewHeight = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0) + win.scrollY;
3559
+ if (scrollViewHeight > this.maxScrollViewDepth) {
3560
+ this.maxScrollViewDepth = scrollViewHeight;
3561
+ }
3562
+ this.previousScrollHeight = document$1.body.scrollHeight;
3563
+ }.bind(this);
3564
+
3565
+ var scrollEndPolyfill = getPolyfillScrollEndFunction(updateScrollDepth);
3566
+ this.listenerScrollDepth = scrollEndPolyfill.listener;
3567
+ win.addEventListener(scrollEndPolyfill.eventType, this.listenerScrollDepth);
3568
+ };
3569
+
2880
3570
  Autocapture.prototype.initClickTracking = function() {
2881
3571
  win.removeEventListener(EV_CLICK, this.listenerClick);
2882
3572
 
@@ -2885,12 +3575,49 @@
2885
3575
  }
2886
3576
  logger$4.log('Initializing click tracking');
2887
3577
 
2888
- this.listenerClick = win.addEventListener(EV_CLICK, function(ev) {
3578
+ this.listenerClick = function(ev) {
2889
3579
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
2890
3580
  return;
2891
3581
  }
2892
3582
  this.trackDomEvent(ev, MP_EV_CLICK);
2893
- }.bind(this));
3583
+ }.bind(this);
3584
+ win.addEventListener(EV_CLICK, this.listenerClick);
3585
+ };
3586
+
3587
+ Autocapture.prototype.initDeadClickTracking = function() {
3588
+ var deadClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK);
3589
+
3590
+ if (!deadClickConfig && !this.mp.get_config('record_heatmap_data')) {
3591
+ this.stopDeadClickTracking();
3592
+ return;
3593
+ }
3594
+
3595
+ logger$4.log('Initializing dead click tracking');
3596
+ if (!this._deadClickTracker) {
3597
+ this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
3598
+ this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
3599
+ }.bind(this));
3600
+ this._deadClickTracker.startTracking();
3601
+ }
3602
+
3603
+ if (!this.listenerDeadClick) {
3604
+ this.listenerDeadClick = function(ev) {
3605
+ var currentDeadClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK);
3606
+ if (!currentDeadClickConfig && !this.mp.is_recording_heatmap_data()) {
3607
+ return;
3608
+ }
3609
+ if (this.currentUrlBlocked()) {
3610
+ return;
3611
+ }
3612
+ // Normalize config to ensure timeout_ms is always set
3613
+ var normalizedConfig = currentDeadClickConfig || {};
3614
+ if (!normalizedConfig['timeout_ms']) {
3615
+ normalizedConfig['timeout_ms'] = DEFAULT_DEAD_CLICK_TIMEOUT_MS;
3616
+ }
3617
+ this._deadClickTracker.trackClick(ev, normalizedConfig);
3618
+ }.bind(this);
3619
+ win.addEventListener(EV_CLICK, this.listenerDeadClick);
3620
+ }
2894
3621
  };
2895
3622
 
2896
3623
  Autocapture.prototype.initInputTracking = function() {
@@ -2901,17 +3628,16 @@
2901
3628
  }
2902
3629
  logger$4.log('Initializing input tracking');
2903
3630
 
2904
- this.listenerChange = win.addEventListener(EV_CHANGE, function(ev) {
3631
+ this.listenerChange = function(ev) {
2905
3632
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
2906
3633
  return;
2907
3634
  }
2908
3635
  this.trackDomEvent(ev, MP_EV_INPUT);
2909
- }.bind(this));
3636
+ }.bind(this);
3637
+ win.addEventListener(EV_CHANGE, this.listenerChange);
2910
3638
  };
2911
3639
 
2912
3640
  Autocapture.prototype.initPageviewTracking = function() {
2913
- win.removeEventListener(EV_POPSTATE, this.listenerPopstate);
2914
- win.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
2915
3641
  win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
2916
3642
 
2917
3643
  if (!this.pageviewTrackingConfig()) {
@@ -2928,27 +3654,7 @@
2928
3654
  previousTrackedUrl = _.info.currentUrl();
2929
3655
  }
2930
3656
 
2931
- this.listenerPopstate = win.addEventListener(EV_POPSTATE, function() {
2932
- win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
2933
- });
2934
- this.listenerHashchange = win.addEventListener(EV_HASHCHANGE, function() {
2935
- win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
2936
- });
2937
- var nativePushState = win.history.pushState;
2938
- if (typeof nativePushState === 'function') {
2939
- win.history.pushState = function(state, unused, url) {
2940
- nativePushState.call(win.history, state, unused, url);
2941
- win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
2942
- };
2943
- }
2944
- var nativeReplaceState = win.history.replaceState;
2945
- if (typeof nativeReplaceState === 'function') {
2946
- win.history.replaceState = function(state, unused, url) {
2947
- nativeReplaceState.call(win.history, state, unused, url);
2948
- win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
2949
- };
2950
- }
2951
- this.listenerLocationchange = win.addEventListener(EV_MP_LOCATION_CHANGE, safewrap(function() {
3657
+ this.listenerLocationchange = safewrap(function() {
2952
3658
  if (this.currentUrlBlocked()) {
2953
3659
  return;
2954
3660
  }
@@ -2975,13 +3681,14 @@
2975
3681
  logger$4.log('Path change: re-initializing scroll depth checkpoints');
2976
3682
  }
2977
3683
  }
2978
- }.bind(this)));
3684
+ }.bind(this));
3685
+ win.addEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
2979
3686
  };
2980
3687
 
2981
3688
  Autocapture.prototype.initRageClickTracking = function() {
2982
3689
  win.removeEventListener(EV_CLICK, this.listenerRageClick);
2983
3690
 
2984
- var rageClickConfig = this._getRageClickConfig();
3691
+ var rageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
2985
3692
  if (!rageClickConfig && !this.mp.get_config('record_heatmap_data')) {
2986
3693
  return;
2987
3694
  }
@@ -2992,7 +3699,7 @@
2992
3699
  }
2993
3700
 
2994
3701
  this.listenerRageClick = function(ev) {
2995
- var currentRageClickConfig = this._getRageClickConfig();
3702
+ var currentRageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
2996
3703
  if (!currentRageClickConfig && !this.mp.is_recording_heatmap_data()) {
2997
3704
  return;
2998
3705
  }
@@ -3010,6 +3717,8 @@
3010
3717
 
3011
3718
  Autocapture.prototype.initScrollTracking = function() {
3012
3719
  win.removeEventListener(EV_SCROLLEND, this.listenerScroll);
3720
+ win.removeEventListener(EV_SCROLL, this.listenerScroll);
3721
+
3013
3722
 
3014
3723
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
3015
3724
  return;
@@ -3017,7 +3726,7 @@
3017
3726
  logger$4.log('Initializing scroll tracking');
3018
3727
  this.lastScrollCheckpoint = 0;
3019
3728
 
3020
- this.listenerScroll = win.addEventListener(EV_SCROLLEND, safewrap(function() {
3729
+ var scrollTrackFunction = function() {
3021
3730
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
3022
3731
  return;
3023
3732
  }
@@ -3056,7 +3765,11 @@
3056
3765
  if (shouldTrack) {
3057
3766
  this.mp.track(MP_EV_SCROLL, props);
3058
3767
  }
3059
- }.bind(this)));
3768
+ }.bind(this);
3769
+
3770
+ var scrollEndPolyfill = getPolyfillScrollEndFunction(scrollTrackFunction);
3771
+ this.listenerScroll = scrollEndPolyfill.listener;
3772
+ win.addEventListener(scrollEndPolyfill.eventType, this.listenerScroll);
3060
3773
  };
3061
3774
 
3062
3775
  Autocapture.prototype.initSubmitTracking = function() {
@@ -3067,18 +3780,81 @@
3067
3780
  }
3068
3781
  logger$4.log('Initializing submit tracking');
3069
3782
 
3070
- this.listenerSubmit = win.addEventListener(EV_SUBMIT, function(ev) {
3783
+ this.listenerSubmit = function(ev) {
3071
3784
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
3072
3785
  return;
3073
3786
  }
3074
3787
  this.trackDomEvent(ev, MP_EV_SUBMIT);
3788
+ }.bind(this);
3789
+ win.addEventListener(EV_SUBMIT, this.listenerSubmit);
3790
+ };
3791
+
3792
+ Autocapture.prototype.initPageLeaveTracking = function() {
3793
+ // Capture page_leave both when the user navigates away from the page (visibilitychange) as well
3794
+ // as when they navigate to a different page within the SPA (popstate/pushstate/hashchange).
3795
+ document$1.removeEventListener(EV_VISIBILITYCHANGE, this.listenerPageLeaveVisibilitychange);
3796
+ win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerPageLeaveLocationchange);
3797
+ win.removeEventListener(EV_LOAD, this.listenerPageLoad);
3798
+
3799
+ if (!this.getConfig(CONFIG_TRACK_PAGE_LEAVE) && !this.mp.get_config('record_heatmap_data')) {
3800
+ return;
3801
+ }
3802
+
3803
+ logger$4.log('Initializing page visibility tracking.');
3804
+ this._initScrollDepthTracking();
3805
+ var previousTrackedUrl = _.info.currentUrl();
3806
+
3807
+ // Initialize previousScrollHeight on `load` which handles async loading
3808
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event
3809
+ this.listenerPageLoad = function() {
3810
+ this.previousScrollHeight = document$1.body.scrollHeight;
3811
+ }.bind(this);
3812
+ win.addEventListener(EV_LOAD, this.listenerPageLoad);
3813
+
3814
+ // Track page navigation events similar to how initPageviewTracking does it
3815
+ this.listenerPageLeaveLocationchange = safewrap(function(ev) {
3816
+ if (this.currentUrlBlocked()) {
3817
+ return;
3818
+ }
3819
+
3820
+ var currentUrl = _.info.currentUrl();
3821
+ // Track all URL changes including query string or fragment changes as separate scroll sessions
3822
+ var shouldTrack = currentUrl !== previousTrackedUrl;
3823
+
3824
+ if (shouldTrack) {
3825
+ this._trackPageLeave(ev, previousTrackedUrl, this.previousScrollHeight);
3826
+ previousTrackedUrl = currentUrl;
3827
+ // Fragment navigation should call scroll(end) and trigger listener, don't add window.scrollY here.
3828
+ this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
3829
+ this.previousScrollHeight = document$1.body.scrollHeight;
3830
+ this.hasTrackedScrollSession = false;
3831
+ }
3075
3832
  }.bind(this));
3833
+ win.addEventListener(EV_MP_LOCATION_CHANGE, this.listenerPageLeaveLocationchange);
3834
+
3835
+ this.listenerPageLeaveVisibilitychange = function(ev) {
3836
+ if (document$1.hidden) {
3837
+ this._trackPageLeave(ev, previousTrackedUrl, this.previousScrollHeight);
3838
+ }
3839
+ }.bind(this);
3840
+ document$1.addEventListener(EV_VISIBILITYCHANGE, this.listenerPageLeaveVisibilitychange);
3841
+ };
3842
+
3843
+ Autocapture.prototype.stopDeadClickTracking = function() {
3844
+ if (this.listenerDeadClick) {
3845
+ win.removeEventListener(EV_CLICK, this.listenerDeadClick);
3846
+ this.listenerDeadClick = null;
3847
+ }
3848
+
3849
+ if (this._deadClickTracker) {
3850
+ this._deadClickTracker.stopTracking();
3851
+ this._deadClickTracker = null;
3852
+ }
3076
3853
  };
3077
3854
 
3078
3855
  // TODO integrate error_reporter from mixpanel instance
3079
3856
  safewrapClass(Autocapture);
3080
3857
 
3081
- var fetch = win['fetch'];
3082
3858
  var logger$3 = console_with_prefix('flags');
3083
3859
 
3084
3860
  var FLAGS_CONFIG_KEY = 'flags';
@@ -3092,6 +3868,7 @@
3092
3868
  * @constructor
3093
3869
  */
3094
3870
  var FeatureFlagManager = function(initOptions) {
3871
+ this.fetch = win['fetch'];
3095
3872
  this.getFullApiRoute = initOptions.getFullApiRoute;
3096
3873
  this.getMpConfig = initOptions.getConfigFunc;
3097
3874
  this.setMpConfig = initOptions.setConfigFunc;
@@ -3100,7 +3877,7 @@
3100
3877
  };
3101
3878
 
3102
3879
  FeatureFlagManager.prototype.init = function() {
3103
- if (!minApisSupported()) {
3880
+ if (!this.minApisSupported()) {
3104
3881
  logger$3.critical('Feature Flags unavailable: missing minimum required APIs');
3105
3882
  return;
3106
3883
  }
@@ -3163,6 +3940,7 @@
3163
3940
 
3164
3941
  var distinctId = this.getMpProperty('distinct_id');
3165
3942
  var deviceId = this.getMpProperty('$device_id');
3943
+ var traceparent = generateTraceparent();
3166
3944
  logger$3.log('Fetching flags for distinct ID: ' + distinctId);
3167
3945
 
3168
3946
  var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
@@ -3174,10 +3952,11 @@
3174
3952
  var url = this.getFullApiRoute() + '?' + searchParams.toString();
3175
3953
 
3176
3954
  this._fetchInProgressStartTime = Date.now();
3177
- this.fetchPromise = win['fetch'](url, {
3955
+ this.fetchPromise = this.fetch.call(win, url, {
3178
3956
  'method': 'GET',
3179
3957
  'headers': {
3180
- 'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':')
3958
+ 'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
3959
+ 'traceparent': traceparent
3181
3960
  }
3182
3961
  }).then(function(response) {
3183
3962
  this.markFetchComplete();
@@ -3190,10 +3969,14 @@
3190
3969
  _.each(responseFlags, function(data, key) {
3191
3970
  flags.set(key, {
3192
3971
  'key': data['variant_key'],
3193
- 'value': data['variant_value']
3972
+ 'value': data['variant_value'],
3973
+ 'experiment_id': data['experiment_id'],
3974
+ 'is_experiment_active': data['is_experiment_active'],
3975
+ 'is_qa_tester': data['is_qa_tester']
3194
3976
  });
3195
3977
  });
3196
3978
  this.flags = flags;
3979
+ this._traceparent = traceparent;
3197
3980
  }.bind(this)).catch(function(error) {
3198
3981
  this.markFetchComplete();
3199
3982
  logger$3.error(error);
@@ -3290,22 +4073,36 @@
3290
4073
  return;
3291
4074
  }
3292
4075
  this.trackedFeatures.add(featureName);
3293
- this.track('$experiment_started', {
4076
+
4077
+ var trackingProperties = {
3294
4078
  'Experiment name': featureName,
3295
4079
  'Variant name': feature['key'],
3296
4080
  '$experiment_type': 'feature_flag',
3297
4081
  'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
3298
4082
  'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
3299
- 'Variant fetch latency (ms)': this._fetchLatency
3300
- });
4083
+ 'Variant fetch latency (ms)': this._fetchLatency,
4084
+ 'Variant fetch traceparent': this._traceparent,
4085
+ };
4086
+
4087
+ if (feature['experiment_id'] !== 'undefined') {
4088
+ trackingProperties['$experiment_id'] = feature['experiment_id'];
4089
+ }
4090
+ if (feature['is_experiment_active'] !== 'undefined') {
4091
+ trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
4092
+ }
4093
+ if (feature['is_qa_tester'] !== 'undefined') {
4094
+ trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
4095
+ }
4096
+
4097
+ this.track('$experiment_started', trackingProperties);
3301
4098
  };
3302
4099
 
3303
- function minApisSupported() {
3304
- return !!fetch &&
4100
+ FeatureFlagManager.prototype.minApisSupported = function() {
4101
+ return !!this.fetch &&
3305
4102
  typeof Promise !== 'undefined' &&
3306
4103
  typeof Map !== 'undefined' &&
3307
4104
  typeof Set !== 'undefined';
3308
- }
4105
+ };
3309
4106
 
3310
4107
  safewrapClass(FeatureFlagManager);
3311
4108