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