@adobe/helix-rum-enhancer 2.14.0 → 2.14.2
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/.husky/pre-commit +1 -0
- package/CHANGELOG.md +14 -0
- package/modules/defaults.js +13 -0
- package/modules/dom.js +66 -0
- package/modules/fflags.js +21 -0
- package/modules/index.js +345 -0
- package/modules/utils.js +38 -0
- package/package.json +4 -4
- package/rollup.config.js +8 -2
- package/src/index.js +379 -359
- package/dist/rum-enhancer.js +0 -416
package/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -9,406 +9,426 @@
|
|
|
9
9
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
12
|
+
|
|
13
|
+
/* eslint-disable max-classes-per-file, wrap-iife */
|
|
14
|
+
// eslint-disable-next-line func-names
|
|
15
|
+
(function () {
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const KNOWN_PROPERTIES = ['weight', 'id', 'referer', 'checkpoint', 't', 'source', 'target', 'cwv', 'CLS', 'FID', 'LCP', 'INP', 'TTFB'];
|
|
19
|
+
const DEFAULT_TRACKING_EVENTS = ['click', 'cwv', 'form', 'enterleave', 'viewblock', 'viewmedia', 'loadresource', 'utm'];
|
|
20
|
+
|
|
21
|
+
const fflags = {
|
|
22
|
+
has: (flag) => fflags[flag].indexOf(Array.from(window.origin)
|
|
23
|
+
.map((a) => a.charCodeAt(0))
|
|
24
|
+
.reduce((a, b) => a + b, 1) % 1371) !== -1,
|
|
25
|
+
enabled: (flag, callback) => fflags.has(flag) && callback(),
|
|
26
|
+
disabled: (flag, callback) => !fflags.has(flag) && callback(),
|
|
27
|
+
onetrust: [543, 770, 1136],
|
|
28
|
+
ads: [1139, 543],
|
|
29
|
+
email: [1139, 543],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const urlSanitizers = {
|
|
33
|
+
/**
|
|
34
|
+
* Returns the full url.
|
|
35
|
+
* If no url is provided, it defaults to window.location.href.
|
|
36
|
+
* @param {string} url (default: window.location.href) The url to sanitize
|
|
37
|
+
* @returns {string} The sanitized url
|
|
38
|
+
*/
|
|
39
|
+
full: (url = window.location.href) => new URL(url).toString(),
|
|
40
|
+
/**
|
|
41
|
+
* Returns the origin of the provided url.
|
|
42
|
+
* If no url is provided, it defaults to window.location.href.
|
|
43
|
+
* @param {string} url (default: window.location.href) The url to sanitize
|
|
44
|
+
* @returns {string} The sanitized url
|
|
45
|
+
*/
|
|
46
|
+
origin: (url = window.location.href) => new URL(url).origin,
|
|
47
|
+
/**
|
|
48
|
+
* Returns the sanitized url: the origin and the path (no query params or hash)
|
|
49
|
+
* If no url is provided, it defaults to window.location.href.
|
|
50
|
+
* @param {string} url (default: window.location.href) The url to sanitize
|
|
51
|
+
* @returns {string} The sanitized url
|
|
52
|
+
*/
|
|
53
|
+
path: (url = window.location.href) => {
|
|
54
|
+
const u = new URL(url);
|
|
55
|
+
return `${u.origin}${u.pathname}`;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const getTargetValue = (element) => element.getAttribute('data-rum-target') || element.getAttribute('href')
|
|
60
|
+
|| element.currentSrc || element.getAttribute('src') || element.dataset.action || element.action;
|
|
61
|
+
|
|
62
|
+
const targetSelector = (element) => {
|
|
63
|
+
try {
|
|
64
|
+
if (!element) return undefined;
|
|
65
|
+
let value = getTargetValue(element);
|
|
66
|
+
if (!value && element.tagName !== 'A' && element.closest('a')) {
|
|
67
|
+
value = getTargetValue(element.closest('a'));
|
|
68
|
+
}
|
|
69
|
+
if (value && !value.startsWith('https://')) {
|
|
45
70
|
// resolve relative links
|
|
46
|
-
|
|
71
|
+
value = new URL(value, window.location).href;
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return null;
|
|
47
76
|
}
|
|
48
|
-
|
|
49
|
-
} catch (error) {
|
|
50
|
-
// something went wrong
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
77
|
+
};
|
|
54
78
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
79
|
+
const sourceSelector = (element) => {
|
|
80
|
+
try {
|
|
81
|
+
if (!element || element === document.body || element === document.documentElement) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
if (element.getAttribute('data-rum-source')) {
|
|
85
|
+
return element.getAttribute('data-rum-source');
|
|
86
|
+
}
|
|
87
|
+
const form = element.closest('form');
|
|
88
|
+
let formElementSelector = '';
|
|
89
|
+
if (form && Array.from(form.elements).includes(element)) {
|
|
90
|
+
formElementSelector = element.tagName === 'INPUT' ? `form input[type='${element.getAttribute('type')}']` : `form ${element.tagName.toLowerCase()}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const blockName = element.closest('.block') ? element.closest('.block').getAttribute('data-block-name') : '';
|
|
94
|
+
if (element.id || formElementSelector) {
|
|
95
|
+
const id = element.id ? `#${element.id}` : '';
|
|
96
|
+
return blockName ? `.${blockName} ${formElementSelector}${id}` : `${formElementSelector}${id}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (element.getAttribute('data-block-name')) {
|
|
100
|
+
return `.${element.getAttribute('data-block-name')}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (Array.from(element.classList).some((className) => className.match(/button|cta/))) {
|
|
104
|
+
return blockName ? `.${blockName} .button` : '.button';
|
|
105
|
+
}
|
|
68
106
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return blockName ? `.${blockName} ${formElementSelector}${id}` : `${formElementSelector}${id}`;
|
|
107
|
+
return sourceSelector(element.parentElement);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return null;
|
|
73
110
|
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const { sampleRUM, queue, isSelected } = (window.hlx && window.hlx.rum) ? window.hlx.rum : {};
|
|
114
|
+
|
|
115
|
+
const formSubmitListener = (e) => sampleRUM('formsubmit', { target: targetSelector(e.target), source: sourceSelector(e.target) });
|
|
116
|
+
// eslint-disable-next-line no-use-before-define
|
|
117
|
+
const mutationObserver = window.MutationObserver ? new MutationObserver(mutationsCallback) : null;
|
|
118
|
+
// Gets configured collection from the config service for the current domain
|
|
119
|
+
function getCollectionConfig() {
|
|
120
|
+
// eslint-disable-next-line max-len
|
|
121
|
+
fflags.enabled('onetrust', () => DEFAULT_TRACKING_EVENTS.push('consent'));
|
|
122
|
+
fflags.enabled('ads', () => DEFAULT_TRACKING_EVENTS.push('paid'));
|
|
123
|
+
fflags.enabled('email', () => DEFAULT_TRACKING_EVENTS.push('email'));
|
|
124
|
+
return DEFAULT_TRACKING_EVENTS;
|
|
125
|
+
}
|
|
74
126
|
|
|
75
|
-
|
|
76
|
-
|
|
127
|
+
function trackCheckpoint(checkpoint, data, t) {
|
|
128
|
+
const { weight, id } = window.hlx.rum;
|
|
129
|
+
if (isSelected) {
|
|
130
|
+
const sendPing = (pdata = data) => {
|
|
131
|
+
// eslint-disable-next-line object-curly-newline, max-len
|
|
132
|
+
const body = JSON.stringify({ weight, id, referer: urlSanitizers[window.hlx.RUM_MASK_URL || 'path'](), checkpoint, t, ...data }, KNOWN_PROPERTIES);
|
|
133
|
+
const { href: url, origin } = new URL(`.rum/${weight}`, sampleRUM.collectBaseURL || sampleRUM.baseURL);
|
|
134
|
+
if (window.location.origin === origin) {
|
|
135
|
+
const headers = { type: 'application/json' };
|
|
136
|
+
navigator.sendBeacon(url, new Blob([body], headers));
|
|
137
|
+
} else {
|
|
138
|
+
navigator.sendBeacon(url, body);
|
|
139
|
+
}
|
|
140
|
+
// eslint-disable-next-line no-console
|
|
141
|
+
console.debug(`ping:${checkpoint}`, pdata);
|
|
142
|
+
};
|
|
143
|
+
sendPing(data);
|
|
77
144
|
}
|
|
145
|
+
}
|
|
78
146
|
|
|
79
|
-
|
|
80
|
-
|
|
147
|
+
function processQueue() {
|
|
148
|
+
while (queue.length) {
|
|
149
|
+
const ck = queue.shift();
|
|
150
|
+
trackCheckpoint(...ck);
|
|
81
151
|
}
|
|
152
|
+
}
|
|
82
153
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
154
|
+
function addCWVTracking() {
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
try {
|
|
157
|
+
const cwvScript = new URL('.rum/web-vitals/dist/web-vitals.iife.js', sampleRUM.baseURL).href;
|
|
158
|
+
if (document.querySelector(`script[src="${cwvScript}"]`)) {
|
|
159
|
+
// web vitals script has been loaded already
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const script = document.createElement('script');
|
|
163
|
+
script.src = cwvScript;
|
|
164
|
+
script.onload = () => {
|
|
165
|
+
const storeCWV = (measurement) => {
|
|
166
|
+
const data = { cwv: {} };
|
|
167
|
+
data.cwv[measurement.name] = measurement.value;
|
|
168
|
+
if (measurement.name === 'LCP' && measurement.entries.length > 0) {
|
|
169
|
+
const { element } = measurement.entries.pop();
|
|
170
|
+
data.target = targetSelector(element);
|
|
171
|
+
data.source = sourceSelector(element) || (element && element.outerHTML.slice(0, 30));
|
|
172
|
+
}
|
|
173
|
+
sampleRUM('cwv', data);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const featureToggle = () => window.location.hostname === 'blog.adobe.com';
|
|
177
|
+
const isEager = (metric) => ['CLS', 'LCP'].includes(metric);
|
|
178
|
+
|
|
179
|
+
// When loading `web-vitals` using a classic script, all the public
|
|
180
|
+
// methods can be found on the `webVitals` global namespace.
|
|
181
|
+
['FID', 'INP', 'TTFB', 'CLS', 'LCP'].forEach((metric) => {
|
|
182
|
+
const metricFn = window.webVitals[`on${metric}`];
|
|
183
|
+
if (typeof metricFn === 'function') {
|
|
184
|
+
const opts = isEager(metric) ? { reportAllChanges: featureToggle() } : undefined;
|
|
185
|
+
metricFn(storeCWV, opts);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
document.head.appendChild(script);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
// something went wrong
|
|
192
|
+
}
|
|
193
|
+
}, 2000); // wait for delayed
|
|
86
194
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function getCollectionConfig() {
|
|
100
|
-
// eslint-disable-next-line max-len
|
|
101
|
-
fflags.enabled('onetrust', () => DEFAULT_TRACKING_EVENTS.push('consent'));
|
|
102
|
-
fflags.enabled('ads', () => DEFAULT_TRACKING_EVENTS.push('paid'));
|
|
103
|
-
fflags.enabled('email', () => DEFAULT_TRACKING_EVENTS.push('email'));
|
|
104
|
-
return DEFAULT_TRACKING_EVENTS;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function trackCheckpoint(checkpoint, data, t) {
|
|
108
|
-
const { weight, id } = window.hlx.rum;
|
|
109
|
-
if (optedIn(checkpoint, data) && isSelected) {
|
|
110
|
-
const sendPing = (pdata = data) => {
|
|
111
|
-
// eslint-disable-next-line object-curly-newline, max-len
|
|
112
|
-
const body = JSON.stringify({ weight, id, referer: urlSanitizers[window.hlx.RUM_MASK_URL || 'path'](), checkpoint, t, ...data }, KNOWN_PROPERTIES);
|
|
113
|
-
const { href: url, origin } = new URL(`.rum/${weight}`, sampleRUM.collectBaseURL || sampleRUM.baseURL);
|
|
114
|
-
if (window.location.origin === origin) {
|
|
115
|
-
const headers = { type: 'application/json' };
|
|
116
|
-
navigator.sendBeacon(url, new Blob([body], headers));
|
|
195
|
+
|
|
196
|
+
function addEnterLeaveTracking() {
|
|
197
|
+
// enter checkpoint when referrer is not the current page url
|
|
198
|
+
const navigate = (source, type) => {
|
|
199
|
+
const payload = { source, target: document.visibilityState };
|
|
200
|
+
// reload: same page, navigate: same origin, enter: everything else
|
|
201
|
+
if (type === 'reload' || source === window.location.href) {
|
|
202
|
+
sampleRUM('reload', payload);
|
|
203
|
+
} else if (type && type !== 'navigate') {
|
|
204
|
+
sampleRUM(type, payload); // back, forward, prerender, etc.
|
|
205
|
+
} else if (source && window.location.origin === new URL(source).origin) {
|
|
206
|
+
sampleRUM('navigate', payload); // internal navigation
|
|
117
207
|
} else {
|
|
118
|
-
|
|
208
|
+
sampleRUM('enter', payload); // enter site
|
|
119
209
|
}
|
|
120
|
-
// eslint-disable-next-line no-console
|
|
121
|
-
console.debug(`ping:${checkpoint}`, pdata);
|
|
122
210
|
};
|
|
123
|
-
sendPing(data);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
211
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
212
|
+
new PerformanceObserver((list) => list
|
|
213
|
+
.getEntries().map((entry) => navigate(document.referrer, entry.type)))
|
|
214
|
+
.observe({ type: 'navigation', buffered: true });
|
|
215
|
+
|
|
216
|
+
const leave = ((event) => {
|
|
217
|
+
try {
|
|
218
|
+
if (leave.left || (event.type === 'visibilitychange' && document.visibilityState !== 'hidden')) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
leave.left = true;
|
|
222
|
+
sampleRUM('leave');
|
|
223
|
+
} catch (error) {
|
|
224
|
+
// something went wrong
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
window.addEventListener('visibilitychange', ((event) => leave(event)));
|
|
228
|
+
window.addEventListener('pagehide', ((event) => leave(event)));
|
|
131
229
|
}
|
|
132
|
-
}
|
|
133
230
|
|
|
134
|
-
function
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
231
|
+
function addLoadResourceTracking() {
|
|
232
|
+
const observer = new PerformanceObserver((list) => {
|
|
233
|
+
try {
|
|
234
|
+
list.getEntries()
|
|
235
|
+
.filter((entry) => !entry.responseStatus || entry.responseStatus < 400)
|
|
236
|
+
.filter((entry) => window.location.hostname === new URL(entry.name).hostname)
|
|
237
|
+
.filter((entry) => new URL(entry.name).pathname.match('.*(\\.plain\\.html|\\.json)$'))
|
|
238
|
+
.forEach((entry) => {
|
|
239
|
+
sampleRUM('loadresource', { source: entry.name, target: Math.round(entry.duration) });
|
|
240
|
+
});
|
|
241
|
+
list.getEntries()
|
|
242
|
+
.filter((entry) => entry.responseStatus === 404)
|
|
243
|
+
.forEach((entry) => {
|
|
244
|
+
sampleRUM('missingresource', { source: entry.name, target: entry.hostname });
|
|
245
|
+
});
|
|
246
|
+
} catch (error) {
|
|
247
|
+
// something went wrong
|
|
141
248
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const storeCWV = (measurement) => {
|
|
146
|
-
const data = { cwv: {} };
|
|
147
|
-
data.cwv[measurement.name] = measurement.value;
|
|
148
|
-
if (measurement.name === 'LCP' && measurement.entries.length > 0) {
|
|
149
|
-
const { element } = measurement.entries.pop();
|
|
150
|
-
data.target = targetselector(element);
|
|
151
|
-
data.source = sourceselector(element) || (element && element.outerHTML.slice(0, 30));
|
|
152
|
-
}
|
|
153
|
-
sampleRUM('cwv', data);
|
|
154
|
-
};
|
|
249
|
+
});
|
|
250
|
+
observer.observe({ type: 'resource', buffered: true });
|
|
251
|
+
}
|
|
155
252
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// When loading `web-vitals` using a classic script, all the public
|
|
160
|
-
// methods can be found on the `webVitals` global namespace.
|
|
161
|
-
['FID', 'INP', 'TTFB', 'CLS', 'LCP'].forEach((metric) => {
|
|
162
|
-
const metricFn = window.webVitals[`on${metric}`];
|
|
163
|
-
if (typeof metricFn === 'function') {
|
|
164
|
-
const opts = isEager(metric) ? { reportAllChanges: featureToggle() } : undefined;
|
|
165
|
-
metricFn(storeCWV, opts);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
};
|
|
169
|
-
document.head.appendChild(script);
|
|
170
|
-
} catch (error) {
|
|
171
|
-
// something went wrong
|
|
172
|
-
}
|
|
173
|
-
}, 2000); // wait for delayed
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function addEnterLeaveTracking() {
|
|
177
|
-
// enter checkpoint when referrer is not the current page url
|
|
178
|
-
const navigate = (source, type) => {
|
|
179
|
-
const payload = { source, target: document.visibilityState };
|
|
180
|
-
// reload: same page, navigate: same origin, enter: everything else
|
|
181
|
-
if (type === 'reload' || source === window.location.href) {
|
|
182
|
-
sampleRUM('reload', payload);
|
|
183
|
-
} else if (type && type !== 'navigate') {
|
|
184
|
-
sampleRUM(type, payload); // back, forward, prerender, etc.
|
|
185
|
-
} else if (source && window.location.origin === new URL(source).origin) {
|
|
186
|
-
sampleRUM('navigate', payload); // internal navigation
|
|
187
|
-
} else {
|
|
188
|
-
sampleRUM('enter', payload); // enter site
|
|
253
|
+
function activateBlocksMutationObserver() {
|
|
254
|
+
if (!mutationObserver || mutationObserver.active) {
|
|
255
|
+
return;
|
|
189
256
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
257
|
+
mutationObserver.active = true;
|
|
258
|
+
mutationObserver.observe(
|
|
259
|
+
document.body,
|
|
260
|
+
// eslint-disable-next-line object-curly-newline
|
|
261
|
+
{ subtree: true, attributes: true, attributeFilter: ['data-block-status'] },
|
|
262
|
+
);
|
|
263
|
+
}
|
|
195
264
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
265
|
+
function getIntersectionObsever(checkpoint) {
|
|
266
|
+
if (!window.IntersectionObserver) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
activateBlocksMutationObserver();
|
|
270
|
+
const observer = new IntersectionObserver((entries) => {
|
|
271
|
+
try {
|
|
272
|
+
entries
|
|
273
|
+
.filter((entry) => entry.isIntersecting)
|
|
274
|
+
.forEach((entry) => {
|
|
275
|
+
observer.unobserve(entry.target); // observe only once
|
|
276
|
+
const target = targetSelector(entry.target);
|
|
277
|
+
const source = sourceSelector(entry.target);
|
|
278
|
+
sampleRUM(checkpoint, { target, source });
|
|
279
|
+
});
|
|
280
|
+
} catch (error) {
|
|
281
|
+
// something went wrong
|
|
200
282
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
283
|
+
}, { threshold: 0.25 });
|
|
284
|
+
return observer;
|
|
285
|
+
}
|
|
286
|
+
function addViewBlockTracking(element) {
|
|
287
|
+
const blockobserver = getIntersectionObsever('viewblock');
|
|
288
|
+
if (blockobserver) {
|
|
289
|
+
const blocks = element.getAttribute('data-block-status') ? [element] : element.querySelectorAll('div[data-block-status="loaded"]');
|
|
290
|
+
blocks.forEach((b) => blockobserver.observe(b));
|
|
205
291
|
}
|
|
206
|
-
}
|
|
207
|
-
window.addEventListener('visibilitychange', ((event) => leave(event)));
|
|
208
|
-
window.addEventListener('pagehide', ((event) => leave(event)));
|
|
209
|
-
}
|
|
292
|
+
}
|
|
210
293
|
|
|
211
|
-
function
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
sampleRUM('loadresource', { source: entry.name, target: Math.round(entry.duration) });
|
|
220
|
-
});
|
|
221
|
-
list.getEntries()
|
|
222
|
-
.filter((entry) => entry.responseStatus === 404)
|
|
223
|
-
.forEach((entry) => {
|
|
224
|
-
sampleRUM('missingresource', { source: entry.name, target: entry.hostname });
|
|
225
|
-
});
|
|
226
|
-
} catch (error) {
|
|
227
|
-
// something went wrong
|
|
294
|
+
function addViewMediaTracking(parent) {
|
|
295
|
+
const mediaobserver = getIntersectionObsever('viewmedia');
|
|
296
|
+
if (mediaobserver) {
|
|
297
|
+
parent.querySelectorAll('img, video, audio, iframe').forEach((m) => {
|
|
298
|
+
if (!m.closest('div .block') || m.closest('div[data-block-status="loaded"]')) {
|
|
299
|
+
mediaobserver.observe(m);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
228
302
|
}
|
|
229
|
-
}
|
|
230
|
-
observer.observe({ type: 'resource', buffered: true });
|
|
231
|
-
}
|
|
303
|
+
}
|
|
232
304
|
|
|
233
|
-
function
|
|
234
|
-
|
|
235
|
-
|
|
305
|
+
function addUTMParametersTracking() {
|
|
306
|
+
const usp = new URLSearchParams(window.location.search);
|
|
307
|
+
[...usp.entries()]
|
|
308
|
+
.filter(([key]) => key.startsWith('utm_'))
|
|
309
|
+
// exclude keys that may leak PII
|
|
310
|
+
.filter(([key]) => key !== 'utm_id')
|
|
311
|
+
.filter(([key]) => key !== 'utm_term')
|
|
312
|
+
.forEach(([source, target]) => sampleRUM('utm', { source, target }));
|
|
236
313
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
314
|
+
|
|
315
|
+
function addAdsParametersTracking() {
|
|
316
|
+
const networks = {
|
|
317
|
+
google: /gclid|gclsrc|wbraid|gbraid/,
|
|
318
|
+
doubleclick: /dclid/,
|
|
319
|
+
microsoft: /msclkid/,
|
|
320
|
+
facebook: /fb(cl|ad_|pxl_)id/,
|
|
321
|
+
twitter: /tw(clid|src|term)/,
|
|
322
|
+
linkedin: /li_fat_id/,
|
|
323
|
+
pinterest: /epik/,
|
|
324
|
+
tiktok: /ttclid/,
|
|
325
|
+
};
|
|
326
|
+
const params = Array.from(new URLSearchParams(window.location.search).keys());
|
|
327
|
+
Object.entries(networks).forEach(([network, regex]) => {
|
|
328
|
+
params.filter((param) => regex.test(param)).forEach((param) => sampleRUM('paid', { source: network, target: param }));
|
|
329
|
+
});
|
|
248
330
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
} catch (error) {
|
|
261
|
-
// something went wrong
|
|
262
|
-
}
|
|
263
|
-
}, { threshold: 0.25 });
|
|
264
|
-
return observer;
|
|
265
|
-
}
|
|
266
|
-
function addViewBlockTracking(element) {
|
|
267
|
-
const blockobserver = getIntersectionObsever('viewblock');
|
|
268
|
-
if (blockobserver) {
|
|
269
|
-
const blocks = element.getAttribute('data-block-status') ? [element] : element.querySelectorAll('div[data-block-status="loaded"]');
|
|
270
|
-
blocks.forEach((b) => blockobserver.observe(b));
|
|
331
|
+
|
|
332
|
+
function addEmailParameterTracking() {
|
|
333
|
+
const networks = {
|
|
334
|
+
mailchimp: /mc_(c|e)id/,
|
|
335
|
+
marketo: /mkt_tok/,
|
|
336
|
+
|
|
337
|
+
};
|
|
338
|
+
const params = Array.from(new URLSearchParams(window.location.search).keys());
|
|
339
|
+
Object.entries(networks).forEach(([network, regex]) => {
|
|
340
|
+
params.filter((param) => regex.test(param)).forEach((param) => sampleRUM('email', { source: network, target: param }));
|
|
341
|
+
});
|
|
271
342
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (!m.closest('div .block') || m.closest('div[data-block-status="loaded"]')) {
|
|
279
|
-
mediaobserver.observe(m);
|
|
280
|
-
}
|
|
343
|
+
|
|
344
|
+
function addFormTracking(parent) {
|
|
345
|
+
activateBlocksMutationObserver();
|
|
346
|
+
parent.querySelectorAll('form').forEach((form) => {
|
|
347
|
+
form.removeEventListener('submit', formSubmitListener); // listen only once
|
|
348
|
+
form.addEventListener('submit', formSubmitListener);
|
|
281
349
|
});
|
|
282
350
|
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function addUTMParametersTracking() {
|
|
286
|
-
const usp = new URLSearchParams(window.location.search);
|
|
287
|
-
[...usp.entries()]
|
|
288
|
-
.filter(([key]) => key.startsWith('utm_'))
|
|
289
|
-
// exclude keys that may leak PII
|
|
290
|
-
.filter(([key]) => key !== 'utm_id')
|
|
291
|
-
.filter(([key]) => key !== 'utm_term')
|
|
292
|
-
.forEach(([source, target]) => sampleRUM('utm', { source, target }));
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function addAdsParametersTracking() {
|
|
296
|
-
const networks = {
|
|
297
|
-
google: /gclid|gclsrc|wbraid|gbraid/,
|
|
298
|
-
doubleclick: /dclid/,
|
|
299
|
-
microsoft: /msclkid/,
|
|
300
|
-
facebook: /fb(cl|ad_|pxl_)id/,
|
|
301
|
-
twitter: /tw(clid|src|term)/,
|
|
302
|
-
linkedin: /li_fat_id/,
|
|
303
|
-
pinterest: /epik/,
|
|
304
|
-
tiktok: /ttclid/,
|
|
305
|
-
};
|
|
306
|
-
const params = Array.from(new URLSearchParams(window.location.search).keys());
|
|
307
|
-
Object.entries(networks).forEach(([network, regex]) => {
|
|
308
|
-
params.filter((param) => regex.test(param)).forEach((param) => sampleRUM('paid', { source: network, target: param }));
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
351
|
|
|
312
|
-
function
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
352
|
+
function addCookieConsentTracking() {
|
|
353
|
+
const cmpCookie = document.cookie.split(';')
|
|
354
|
+
.map((c) => c.trim())
|
|
355
|
+
.find((cookie) => cookie.startsWith('OptanonAlertBoxClosed='));
|
|
316
356
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function addFormTracking(parent) {
|
|
325
|
-
activateBlocksMutationObserver();
|
|
326
|
-
parent.querySelectorAll('form').forEach((form) => {
|
|
327
|
-
form.removeEventListener('submit', formSubmitListener); // listen only once
|
|
328
|
-
form.addEventListener('submit', formSubmitListener);
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function addCookieConsentTracking() {
|
|
333
|
-
const cmpCookie = document.cookie.split(';')
|
|
334
|
-
.map((c) => c.trim())
|
|
335
|
-
.find((cookie) => cookie.startsWith('OptanonAlertBoxClosed='));
|
|
336
|
-
|
|
337
|
-
if (cmpCookie) {
|
|
338
|
-
sampleRUM('consent', { source: 'onetrust', target: 'hidden' });
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
357
|
+
if (cmpCookie) {
|
|
358
|
+
sampleRUM('consent', { source: 'onetrust', target: 'hidden' });
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
341
361
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
362
|
+
let consentMutationObserver;
|
|
363
|
+
const trackShowConsent = () => {
|
|
364
|
+
if (document.querySelector('body > div#onetrust-consent-sdk')) {
|
|
365
|
+
sampleRUM('consent', { source: 'onetrust', target: 'show' });
|
|
366
|
+
if (consentMutationObserver) {
|
|
367
|
+
consentMutationObserver.disconnect();
|
|
368
|
+
}
|
|
369
|
+
return true;
|
|
348
370
|
}
|
|
349
|
-
return
|
|
350
|
-
}
|
|
351
|
-
return false;
|
|
352
|
-
};
|
|
371
|
+
return false;
|
|
372
|
+
};
|
|
353
373
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
374
|
+
if (!trackShowConsent()) {
|
|
375
|
+
// eslint-disable-next-line max-len
|
|
376
|
+
consentMutationObserver = window.MutationObserver ? new MutationObserver(trackShowConsent) : null;
|
|
377
|
+
if (consentMutationObserver) {
|
|
378
|
+
consentMutationObserver.observe(
|
|
379
|
+
document.body,
|
|
380
|
+
// eslint-disable-next-line object-curly-newline
|
|
381
|
+
{ attributes: false, childList: true, subtree: false },
|
|
382
|
+
);
|
|
383
|
+
}
|
|
363
384
|
}
|
|
364
385
|
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const addObserver = (ck, fn, block) => getCollectionConfig().includes(ck) && fn(block);
|
|
368
|
-
function mutationsCallback(mutations) {
|
|
369
|
-
mutations.filter((m) => m.type === 'attributes' && m.attributeName === 'data-block-status')
|
|
370
|
-
.filter((m) => m.target.dataset.blockStatus === 'loaded')
|
|
371
|
-
.forEach((m) => {
|
|
372
|
-
addObserver('form', addFormTracking, m.target);
|
|
373
|
-
addObserver('viewblock', addViewBlockTracking, m.target);
|
|
374
|
-
addObserver('viewmedia', addViewMediaTracking, m.target);
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
386
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
387
|
+
const addObserver = (ck, fn, block) => getCollectionConfig().includes(ck) && fn(block);
|
|
388
|
+
function mutationsCallback(mutations) {
|
|
389
|
+
mutations.filter((m) => m.type === 'attributes' && m.attributeName === 'data-block-status')
|
|
390
|
+
.filter((m) => m.target.dataset.blockStatus === 'loaded')
|
|
391
|
+
.forEach((m) => {
|
|
392
|
+
addObserver('form', addFormTracking, m.target);
|
|
393
|
+
addObserver('viewblock', addViewBlockTracking, m.target);
|
|
394
|
+
addObserver('viewmedia', addViewMediaTracking, m.target);
|
|
383
395
|
});
|
|
384
|
-
|
|
385
|
-
cwv: () => addCWVTracking(),
|
|
386
|
-
form: () => addFormTracking(window.document.body),
|
|
387
|
-
enterleave: () => addEnterLeaveTracking(),
|
|
388
|
-
loadresource: () => addLoadResourceTracking(),
|
|
389
|
-
utm: () => addUTMParametersTracking(),
|
|
390
|
-
viewblock: () => addViewBlockTracking(window.document.body),
|
|
391
|
-
viewmedia: () => addViewMediaTracking(window.document.body),
|
|
392
|
-
consent: () => addCookieConsentTracking(),
|
|
393
|
-
paid: () => addAdsParametersTracking(),
|
|
394
|
-
email: () => addEmailParameterTracking(),
|
|
395
|
-
};
|
|
396
|
+
}
|
|
396
397
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
398
|
+
function addTrackingFromConfig() {
|
|
399
|
+
const trackingFunctions = {
|
|
400
|
+
click: () => {
|
|
401
|
+
document.addEventListener('click', (event) => {
|
|
402
|
+
sampleRUM('click', { target: targetSelector(event.target), source: sourceSelector(event.target) });
|
|
403
|
+
});
|
|
404
|
+
},
|
|
405
|
+
cwv: () => addCWVTracking(),
|
|
406
|
+
form: () => addFormTracking(window.document.body),
|
|
407
|
+
enterleave: () => addEnterLeaveTracking(),
|
|
408
|
+
loadresource: () => addLoadResourceTracking(),
|
|
409
|
+
utm: () => addUTMParametersTracking(),
|
|
410
|
+
viewblock: () => addViewBlockTracking(window.document.body),
|
|
411
|
+
viewmedia: () => addViewMediaTracking(window.document.body),
|
|
412
|
+
consent: () => addCookieConsentTracking(),
|
|
413
|
+
paid: () => addAdsParametersTracking(),
|
|
414
|
+
email: () => addEmailParameterTracking(),
|
|
415
|
+
};
|
|
400
416
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
417
|
+
getCollectionConfig().filter((ck) => trackingFunctions[ck])
|
|
418
|
+
.forEach((ck) => trackingFunctions[ck]());
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function initEnhancer() {
|
|
422
|
+
try {
|
|
423
|
+
if (sampleRUM) {
|
|
424
|
+
addTrackingFromConfig();
|
|
425
|
+
window.hlx.rum.collector = trackCheckpoint;
|
|
426
|
+
processQueue();
|
|
427
|
+
}
|
|
428
|
+
} catch (error) {
|
|
429
|
+
// something went wrong
|
|
407
430
|
}
|
|
408
|
-
} catch (error) {
|
|
409
|
-
// something went wrong
|
|
410
431
|
}
|
|
411
|
-
}
|
|
412
432
|
|
|
413
|
-
|
|
414
|
-
|
|
433
|
+
initEnhancer();
|
|
434
|
+
})();
|