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.
- 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 +857 -56
- 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 +857 -56
- package/dist/mixpanel-with-recorder.d.ts +440 -0
- package/dist/mixpanel-with-recorder.js +861 -58
- 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 +861 -58
- package/dist/mixpanel.cjs.d.ts +440 -0
- package/dist/mixpanel.cjs.js +861 -58
- package/dist/mixpanel.globals.js +857 -56
- package/dist/mixpanel.min.js +170 -152
- package/dist/mixpanel.module.d.ts +440 -0
- package/dist/mixpanel.module.js +861 -58
- package/dist/mixpanel.umd.d.ts +440 -0
- package/dist/mixpanel.umd.js +861 -58
- 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 +237 -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 +43 -18
- 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.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.
|
|
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,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 =
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
3167
|
-
|
|
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 =
|
|
3171
|
-
'method': '
|
|
3954
|
+
this.fetchPromise = this.fetch.call(win, url, {
|
|
3955
|
+
'method': 'GET',
|
|
3172
3956
|
'headers': {
|
|
3173
3957
|
'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
|
|
3174
|
-
'
|
|
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
|
-
|
|
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
|
|
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
|
|