mixpanel-browser 2.70.0 → 2.71.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +9 -0
- package/CHANGELOG.md +11 -0
- package/build.sh +1 -0
- package/dist/mixpanel-core.cjs.d.ts +440 -0
- package/dist/mixpanel-core.cjs.js +849 -50
- package/dist/mixpanel-recorder.js +5 -3
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +440 -0
- package/dist/mixpanel-with-async-recorder.cjs.js +849 -50
- package/dist/mixpanel-with-recorder.d.ts +440 -0
- package/dist/mixpanel-with-recorder.js +853 -52
- package/dist/mixpanel-with-recorder.min.d.ts +440 -0
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +440 -0
- package/dist/mixpanel.amd.js +853 -52
- package/dist/mixpanel.cjs.d.ts +440 -0
- package/dist/mixpanel.cjs.js +853 -52
- package/dist/mixpanel.globals.js +849 -50
- package/dist/mixpanel.min.js +170 -153
- package/dist/mixpanel.module.d.ts +440 -0
- package/dist/mixpanel.module.js +853 -52
- package/dist/mixpanel.umd.d.ts +440 -0
- package/dist/mixpanel.umd.js +853 -52
- package/dist/rrweb-compiled.js +4 -2
- package/package.json +2 -19
- package/rollup.config.mjs +28 -4
- package/src/autocapture/deadclick.js +254 -0
- package/src/autocapture/index.js +239 -41
- package/src/autocapture/shadow-dom-observer.js +100 -0
- package/src/autocapture/utils.js +230 -3
- package/src/config.js +1 -1
- package/src/flags/index.js +32 -12
- package/src/index.d.ts +16 -3
- package/src/loaders/loader-module-core.d.ts +1 -0
- package/src/loaders/loader-module-with-async-recorder.d.ts +1 -0
- package/src/loaders/loader-module.d.ts +1 -0
- package/src/utils.js +15 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var Config = {
|
|
4
4
|
DEBUG: false,
|
|
5
|
-
LIB_VERSION: '2.
|
|
5
|
+
LIB_VERSION: '2.71.1'
|
|
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.
|
|
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.
|
|
2862
|
-
|
|
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; //
|
|
3493
|
+
return null; // click tracking disabled
|
|
2866
3494
|
}
|
|
2867
3495
|
|
|
2868
3496
|
if (config === true) {
|
|
@@ -2876,6 +3504,70 @@ 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
|
+
|
|
3513
|
+
if (!this.getConfig(CONFIG_TRACK_PAGE_LEAVE) && !this.mp.is_recording_heatmap_data()) {
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
this.hasTrackedScrollSession = true;
|
|
3518
|
+
var viewportHeight = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
|
|
3519
|
+
var scrollPercentage = Math.round(Math.max(this.maxScrollViewDepth - viewportHeight, 0) / (currentScrollHeight - viewportHeight) * 100);
|
|
3520
|
+
var foldLinePercentage = Math.round((viewportHeight / currentScrollHeight) * 100);
|
|
3521
|
+
if (currentScrollHeight <= viewportHeight) {
|
|
3522
|
+
// If the content fits within the viewport, consider it fully scrolled
|
|
3523
|
+
scrollPercentage = 100;
|
|
3524
|
+
foldLinePercentage = 100;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
var props = _.extend({
|
|
3528
|
+
'$max_scroll_view_depth': this.maxScrollViewDepth,
|
|
3529
|
+
'$max_scroll_percentage': scrollPercentage,
|
|
3530
|
+
'$fold_line_percentage': foldLinePercentage,
|
|
3531
|
+
'$scroll_height': currentScrollHeight,
|
|
3532
|
+
'$event_type': ev.type,
|
|
3533
|
+
'$current_url': currentUrl || _.info.currentUrl(),
|
|
3534
|
+
'$viewportHeight': viewportHeight, // This is the fold line
|
|
3535
|
+
'$viewportWidth': Math.max(document$1.documentElement.clientWidth, win.innerWidth || 0),
|
|
3536
|
+
'$captured_for_heatmap': this.mp.is_recording_heatmap_data()
|
|
3537
|
+
}, DEFAULT_PROPS);
|
|
3538
|
+
|
|
3539
|
+
// Send with beacon transport to ensure event is sent before unload
|
|
3540
|
+
this.mp.track(MP_EV_PAGE_LEAVE, props, {transport: 'sendBeacon'});
|
|
3541
|
+
};
|
|
3542
|
+
|
|
3543
|
+
Autocapture.prototype._initScrollDepthTracking = function() {
|
|
3544
|
+
win.removeEventListener(EV_SCROLL, this.listenerScrollDepth);
|
|
3545
|
+
win.removeEventListener(EV_SCROLLEND, this.listenerScrollDepth);
|
|
3546
|
+
|
|
3547
|
+
if (!this.mp.get_config('record_heatmap_data')) {
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
logger$4.log('Initializing scroll depth tracking');
|
|
3552
|
+
|
|
3553
|
+
this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
|
|
3554
|
+
|
|
3555
|
+
var updateScrollDepth = function() {
|
|
3556
|
+
if (this.currentUrlBlocked()) {
|
|
3557
|
+
return;
|
|
3558
|
+
}
|
|
3559
|
+
var scrollViewHeight = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0) + win.scrollY;
|
|
3560
|
+
if (scrollViewHeight > this.maxScrollViewDepth) {
|
|
3561
|
+
this.maxScrollViewDepth = scrollViewHeight;
|
|
3562
|
+
}
|
|
3563
|
+
this.previousScrollHeight = document$1.body.scrollHeight;
|
|
3564
|
+
}.bind(this);
|
|
3565
|
+
|
|
3566
|
+
var scrollEndPolyfill = getPolyfillScrollEndFunction(updateScrollDepth);
|
|
3567
|
+
this.listenerScrollDepth = scrollEndPolyfill.listener;
|
|
3568
|
+
win.addEventListener(scrollEndPolyfill.eventType, this.listenerScrollDepth);
|
|
3569
|
+
};
|
|
3570
|
+
|
|
2879
3571
|
Autocapture.prototype.initClickTracking = function() {
|
|
2880
3572
|
win.removeEventListener(EV_CLICK, this.listenerClick);
|
|
2881
3573
|
|
|
@@ -2884,12 +3576,49 @@ Autocapture.prototype.initClickTracking = function() {
|
|
|
2884
3576
|
}
|
|
2885
3577
|
logger$4.log('Initializing click tracking');
|
|
2886
3578
|
|
|
2887
|
-
this.listenerClick =
|
|
3579
|
+
this.listenerClick = function(ev) {
|
|
2888
3580
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
|
|
2889
3581
|
return;
|
|
2890
3582
|
}
|
|
2891
3583
|
this.trackDomEvent(ev, MP_EV_CLICK);
|
|
2892
|
-
}.bind(this)
|
|
3584
|
+
}.bind(this);
|
|
3585
|
+
win.addEventListener(EV_CLICK, this.listenerClick);
|
|
3586
|
+
};
|
|
3587
|
+
|
|
3588
|
+
Autocapture.prototype.initDeadClickTracking = function() {
|
|
3589
|
+
var deadClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK);
|
|
3590
|
+
|
|
3591
|
+
if (!deadClickConfig && !this.mp.get_config('record_heatmap_data')) {
|
|
3592
|
+
this.stopDeadClickTracking();
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
logger$4.log('Initializing dead click tracking');
|
|
3597
|
+
if (!this._deadClickTracker) {
|
|
3598
|
+
this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
|
|
3599
|
+
this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
|
|
3600
|
+
}.bind(this));
|
|
3601
|
+
this._deadClickTracker.startTracking();
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
if (!this.listenerDeadClick) {
|
|
3605
|
+
this.listenerDeadClick = function(ev) {
|
|
3606
|
+
var currentDeadClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK);
|
|
3607
|
+
if (!currentDeadClickConfig && !this.mp.is_recording_heatmap_data()) {
|
|
3608
|
+
return;
|
|
3609
|
+
}
|
|
3610
|
+
if (this.currentUrlBlocked()) {
|
|
3611
|
+
return;
|
|
3612
|
+
}
|
|
3613
|
+
// Normalize config to ensure timeout_ms is always set
|
|
3614
|
+
var normalizedConfig = currentDeadClickConfig || {};
|
|
3615
|
+
if (!normalizedConfig['timeout_ms']) {
|
|
3616
|
+
normalizedConfig['timeout_ms'] = DEFAULT_DEAD_CLICK_TIMEOUT_MS;
|
|
3617
|
+
}
|
|
3618
|
+
this._deadClickTracker.trackClick(ev, normalizedConfig);
|
|
3619
|
+
}.bind(this);
|
|
3620
|
+
win.addEventListener(EV_CLICK, this.listenerDeadClick);
|
|
3621
|
+
}
|
|
2893
3622
|
};
|
|
2894
3623
|
|
|
2895
3624
|
Autocapture.prototype.initInputTracking = function() {
|
|
@@ -2900,17 +3629,16 @@ Autocapture.prototype.initInputTracking = function() {
|
|
|
2900
3629
|
}
|
|
2901
3630
|
logger$4.log('Initializing input tracking');
|
|
2902
3631
|
|
|
2903
|
-
this.listenerChange =
|
|
3632
|
+
this.listenerChange = function(ev) {
|
|
2904
3633
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
2905
3634
|
return;
|
|
2906
3635
|
}
|
|
2907
3636
|
this.trackDomEvent(ev, MP_EV_INPUT);
|
|
2908
|
-
}.bind(this)
|
|
3637
|
+
}.bind(this);
|
|
3638
|
+
win.addEventListener(EV_CHANGE, this.listenerChange);
|
|
2909
3639
|
};
|
|
2910
3640
|
|
|
2911
3641
|
Autocapture.prototype.initPageviewTracking = function() {
|
|
2912
|
-
win.removeEventListener(EV_POPSTATE, this.listenerPopstate);
|
|
2913
|
-
win.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
|
|
2914
3642
|
win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
|
|
2915
3643
|
|
|
2916
3644
|
if (!this.pageviewTrackingConfig()) {
|
|
@@ -2927,27 +3655,7 @@ Autocapture.prototype.initPageviewTracking = function() {
|
|
|
2927
3655
|
previousTrackedUrl = _.info.currentUrl();
|
|
2928
3656
|
}
|
|
2929
3657
|
|
|
2930
|
-
this.
|
|
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() {
|
|
3658
|
+
this.listenerLocationchange = safewrap(function() {
|
|
2951
3659
|
if (this.currentUrlBlocked()) {
|
|
2952
3660
|
return;
|
|
2953
3661
|
}
|
|
@@ -2974,13 +3682,14 @@ Autocapture.prototype.initPageviewTracking = function() {
|
|
|
2974
3682
|
logger$4.log('Path change: re-initializing scroll depth checkpoints');
|
|
2975
3683
|
}
|
|
2976
3684
|
}
|
|
2977
|
-
}.bind(this))
|
|
3685
|
+
}.bind(this));
|
|
3686
|
+
win.addEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
|
|
2978
3687
|
};
|
|
2979
3688
|
|
|
2980
3689
|
Autocapture.prototype.initRageClickTracking = function() {
|
|
2981
3690
|
win.removeEventListener(EV_CLICK, this.listenerRageClick);
|
|
2982
3691
|
|
|
2983
|
-
var rageClickConfig = this.
|
|
3692
|
+
var rageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
|
|
2984
3693
|
if (!rageClickConfig && !this.mp.get_config('record_heatmap_data')) {
|
|
2985
3694
|
return;
|
|
2986
3695
|
}
|
|
@@ -2991,7 +3700,7 @@ Autocapture.prototype.initRageClickTracking = function() {
|
|
|
2991
3700
|
}
|
|
2992
3701
|
|
|
2993
3702
|
this.listenerRageClick = function(ev) {
|
|
2994
|
-
var currentRageClickConfig = this.
|
|
3703
|
+
var currentRageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
|
|
2995
3704
|
if (!currentRageClickConfig && !this.mp.is_recording_heatmap_data()) {
|
|
2996
3705
|
return;
|
|
2997
3706
|
}
|
|
@@ -3009,6 +3718,8 @@ Autocapture.prototype.initRageClickTracking = function() {
|
|
|
3009
3718
|
|
|
3010
3719
|
Autocapture.prototype.initScrollTracking = function() {
|
|
3011
3720
|
win.removeEventListener(EV_SCROLLEND, this.listenerScroll);
|
|
3721
|
+
win.removeEventListener(EV_SCROLL, this.listenerScroll);
|
|
3722
|
+
|
|
3012
3723
|
|
|
3013
3724
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
3014
3725
|
return;
|
|
@@ -3016,7 +3727,7 @@ Autocapture.prototype.initScrollTracking = function() {
|
|
|
3016
3727
|
logger$4.log('Initializing scroll tracking');
|
|
3017
3728
|
this.lastScrollCheckpoint = 0;
|
|
3018
3729
|
|
|
3019
|
-
|
|
3730
|
+
var scrollTrackFunction = function() {
|
|
3020
3731
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
3021
3732
|
return;
|
|
3022
3733
|
}
|
|
@@ -3055,7 +3766,11 @@ Autocapture.prototype.initScrollTracking = function() {
|
|
|
3055
3766
|
if (shouldTrack) {
|
|
3056
3767
|
this.mp.track(MP_EV_SCROLL, props);
|
|
3057
3768
|
}
|
|
3058
|
-
}.bind(this)
|
|
3769
|
+
}.bind(this);
|
|
3770
|
+
|
|
3771
|
+
var scrollEndPolyfill = getPolyfillScrollEndFunction(scrollTrackFunction);
|
|
3772
|
+
this.listenerScroll = scrollEndPolyfill.listener;
|
|
3773
|
+
win.addEventListener(scrollEndPolyfill.eventType, this.listenerScroll);
|
|
3059
3774
|
};
|
|
3060
3775
|
|
|
3061
3776
|
Autocapture.prototype.initSubmitTracking = function() {
|
|
@@ -3066,18 +3781,81 @@ Autocapture.prototype.initSubmitTracking = function() {
|
|
|
3066
3781
|
}
|
|
3067
3782
|
logger$4.log('Initializing submit tracking');
|
|
3068
3783
|
|
|
3069
|
-
this.listenerSubmit =
|
|
3784
|
+
this.listenerSubmit = function(ev) {
|
|
3070
3785
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
3071
3786
|
return;
|
|
3072
3787
|
}
|
|
3073
3788
|
this.trackDomEvent(ev, MP_EV_SUBMIT);
|
|
3789
|
+
}.bind(this);
|
|
3790
|
+
win.addEventListener(EV_SUBMIT, this.listenerSubmit);
|
|
3791
|
+
};
|
|
3792
|
+
|
|
3793
|
+
Autocapture.prototype.initPageLeaveTracking = function() {
|
|
3794
|
+
// Capture page_leave both when the user navigates away from the page (visibilitychange) as well
|
|
3795
|
+
// as when they navigate to a different page within the SPA (popstate/pushstate/hashchange).
|
|
3796
|
+
document$1.removeEventListener(EV_VISIBILITYCHANGE, this.listenerPageLeaveVisibilitychange);
|
|
3797
|
+
win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerPageLeaveLocationchange);
|
|
3798
|
+
win.removeEventListener(EV_LOAD, this.listenerPageLoad);
|
|
3799
|
+
|
|
3800
|
+
if (!this.getConfig(CONFIG_TRACK_PAGE_LEAVE) && !this.mp.get_config('record_heatmap_data')) {
|
|
3801
|
+
return;
|
|
3802
|
+
}
|
|
3803
|
+
|
|
3804
|
+
logger$4.log('Initializing page visibility tracking.');
|
|
3805
|
+
this._initScrollDepthTracking();
|
|
3806
|
+
var previousTrackedUrl = _.info.currentUrl();
|
|
3807
|
+
|
|
3808
|
+
// Initialize previousScrollHeight on `load` which handles async loading
|
|
3809
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event
|
|
3810
|
+
this.listenerPageLoad = function() {
|
|
3811
|
+
this.previousScrollHeight = document$1.body.scrollHeight;
|
|
3812
|
+
}.bind(this);
|
|
3813
|
+
win.addEventListener(EV_LOAD, this.listenerPageLoad);
|
|
3814
|
+
|
|
3815
|
+
// Track page navigation events similar to how initPageviewTracking does it
|
|
3816
|
+
this.listenerPageLeaveLocationchange = safewrap(function(ev) {
|
|
3817
|
+
if (this.currentUrlBlocked()) {
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
var currentUrl = _.info.currentUrl();
|
|
3822
|
+
// Track all URL changes including query string or fragment changes as separate scroll sessions
|
|
3823
|
+
var shouldTrack = currentUrl !== previousTrackedUrl;
|
|
3824
|
+
|
|
3825
|
+
if (shouldTrack) {
|
|
3826
|
+
this._trackPageLeave(ev, previousTrackedUrl, this.previousScrollHeight);
|
|
3827
|
+
previousTrackedUrl = currentUrl;
|
|
3828
|
+
// Fragment navigation should call scroll(end) and trigger listener, don't add window.scrollY here.
|
|
3829
|
+
this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
|
|
3830
|
+
this.previousScrollHeight = document$1.body.scrollHeight;
|
|
3831
|
+
this.hasTrackedScrollSession = false;
|
|
3832
|
+
}
|
|
3074
3833
|
}.bind(this));
|
|
3834
|
+
win.addEventListener(EV_MP_LOCATION_CHANGE, this.listenerPageLeaveLocationchange);
|
|
3835
|
+
|
|
3836
|
+
this.listenerPageLeaveVisibilitychange = function(ev) {
|
|
3837
|
+
if (document$1.hidden) {
|
|
3838
|
+
this._trackPageLeave(ev, previousTrackedUrl, this.previousScrollHeight);
|
|
3839
|
+
}
|
|
3840
|
+
}.bind(this);
|
|
3841
|
+
document$1.addEventListener(EV_VISIBILITYCHANGE, this.listenerPageLeaveVisibilitychange);
|
|
3842
|
+
};
|
|
3843
|
+
|
|
3844
|
+
Autocapture.prototype.stopDeadClickTracking = function() {
|
|
3845
|
+
if (this.listenerDeadClick) {
|
|
3846
|
+
win.removeEventListener(EV_CLICK, this.listenerDeadClick);
|
|
3847
|
+
this.listenerDeadClick = null;
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
if (this._deadClickTracker) {
|
|
3851
|
+
this._deadClickTracker.stopTracking();
|
|
3852
|
+
this._deadClickTracker = null;
|
|
3853
|
+
}
|
|
3075
3854
|
};
|
|
3076
3855
|
|
|
3077
3856
|
// TODO integrate error_reporter from mixpanel instance
|
|
3078
3857
|
safewrapClass(Autocapture);
|
|
3079
3858
|
|
|
3080
|
-
var fetch = win['fetch'];
|
|
3081
3859
|
var logger$3 = console_with_prefix('flags');
|
|
3082
3860
|
|
|
3083
3861
|
var FLAGS_CONFIG_KEY = 'flags';
|
|
@@ -3091,6 +3869,7 @@ CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
|
|
|
3091
3869
|
* @constructor
|
|
3092
3870
|
*/
|
|
3093
3871
|
var FeatureFlagManager = function(initOptions) {
|
|
3872
|
+
this.fetch = win['fetch'];
|
|
3094
3873
|
this.getFullApiRoute = initOptions.getFullApiRoute;
|
|
3095
3874
|
this.getMpConfig = initOptions.getConfigFunc;
|
|
3096
3875
|
this.setMpConfig = initOptions.setConfigFunc;
|
|
@@ -3099,7 +3878,7 @@ var FeatureFlagManager = function(initOptions) {
|
|
|
3099
3878
|
};
|
|
3100
3879
|
|
|
3101
3880
|
FeatureFlagManager.prototype.init = function() {
|
|
3102
|
-
if (!minApisSupported()) {
|
|
3881
|
+
if (!this.minApisSupported()) {
|
|
3103
3882
|
logger$3.critical('Feature Flags unavailable: missing minimum required APIs');
|
|
3104
3883
|
return;
|
|
3105
3884
|
}
|
|
@@ -3162,6 +3941,7 @@ FeatureFlagManager.prototype.fetchFlags = function() {
|
|
|
3162
3941
|
|
|
3163
3942
|
var distinctId = this.getMpProperty('distinct_id');
|
|
3164
3943
|
var deviceId = this.getMpProperty('$device_id');
|
|
3944
|
+
var traceparent = generateTraceparent();
|
|
3165
3945
|
logger$3.log('Fetching flags for distinct ID: ' + distinctId);
|
|
3166
3946
|
|
|
3167
3947
|
var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
|
|
@@ -3173,10 +3953,11 @@ FeatureFlagManager.prototype.fetchFlags = function() {
|
|
|
3173
3953
|
var url = this.getFullApiRoute() + '?' + searchParams.toString();
|
|
3174
3954
|
|
|
3175
3955
|
this._fetchInProgressStartTime = Date.now();
|
|
3176
|
-
this.fetchPromise =
|
|
3956
|
+
this.fetchPromise = this.fetch.call(win, url, {
|
|
3177
3957
|
'method': 'GET',
|
|
3178
3958
|
'headers': {
|
|
3179
|
-
'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':')
|
|
3959
|
+
'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
|
|
3960
|
+
'traceparent': traceparent
|
|
3180
3961
|
}
|
|
3181
3962
|
}).then(function(response) {
|
|
3182
3963
|
this.markFetchComplete();
|
|
@@ -3189,10 +3970,14 @@ FeatureFlagManager.prototype.fetchFlags = function() {
|
|
|
3189
3970
|
_.each(responseFlags, function(data, key) {
|
|
3190
3971
|
flags.set(key, {
|
|
3191
3972
|
'key': data['variant_key'],
|
|
3192
|
-
'value': data['variant_value']
|
|
3973
|
+
'value': data['variant_value'],
|
|
3974
|
+
'experiment_id': data['experiment_id'],
|
|
3975
|
+
'is_experiment_active': data['is_experiment_active'],
|
|
3976
|
+
'is_qa_tester': data['is_qa_tester']
|
|
3193
3977
|
});
|
|
3194
3978
|
});
|
|
3195
3979
|
this.flags = flags;
|
|
3980
|
+
this._traceparent = traceparent;
|
|
3196
3981
|
}.bind(this)).catch(function(error) {
|
|
3197
3982
|
this.markFetchComplete();
|
|
3198
3983
|
logger$3.error(error);
|
|
@@ -3289,22 +4074,36 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
|
|
|
3289
4074
|
return;
|
|
3290
4075
|
}
|
|
3291
4076
|
this.trackedFeatures.add(featureName);
|
|
3292
|
-
|
|
4077
|
+
|
|
4078
|
+
var trackingProperties = {
|
|
3293
4079
|
'Experiment name': featureName,
|
|
3294
4080
|
'Variant name': feature['key'],
|
|
3295
4081
|
'$experiment_type': 'feature_flag',
|
|
3296
4082
|
'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
|
|
3297
4083
|
'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
|
|
3298
|
-
'Variant fetch latency (ms)': this._fetchLatency
|
|
3299
|
-
|
|
4084
|
+
'Variant fetch latency (ms)': this._fetchLatency,
|
|
4085
|
+
'Variant fetch traceparent': this._traceparent,
|
|
4086
|
+
};
|
|
4087
|
+
|
|
4088
|
+
if (feature['experiment_id'] !== 'undefined') {
|
|
4089
|
+
trackingProperties['$experiment_id'] = feature['experiment_id'];
|
|
4090
|
+
}
|
|
4091
|
+
if (feature['is_experiment_active'] !== 'undefined') {
|
|
4092
|
+
trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
|
|
4093
|
+
}
|
|
4094
|
+
if (feature['is_qa_tester'] !== 'undefined') {
|
|
4095
|
+
trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
|
|
4096
|
+
}
|
|
4097
|
+
|
|
4098
|
+
this.track('$experiment_started', trackingProperties);
|
|
3300
4099
|
};
|
|
3301
4100
|
|
|
3302
|
-
function
|
|
3303
|
-
return !!fetch &&
|
|
4101
|
+
FeatureFlagManager.prototype.minApisSupported = function() {
|
|
4102
|
+
return !!this.fetch &&
|
|
3304
4103
|
typeof Promise !== 'undefined' &&
|
|
3305
4104
|
typeof Map !== 'undefined' &&
|
|
3306
4105
|
typeof Set !== 'undefined';
|
|
3307
|
-
}
|
|
4106
|
+
};
|
|
3308
4107
|
|
|
3309
4108
|
safewrapClass(FeatureFlagManager);
|
|
3310
4109
|
|