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