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