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