@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/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2023 Adobe. All rights reserved.
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
- /* eslint-env browser */
13
- const KNOWN_PROPERTIES = ['weight', 'id', 'referer', 'checkpoint', 't', 'source', 'target', 'cwv', 'CLS', 'FID', 'LCP', 'INP', 'TTFB'];
14
- const DEFAULT_TRACKING_EVENTS = ['click', 'cwv', 'form', 'enterleave', 'viewblock', 'viewmedia', 'loadresource', 'utm'];
15
- const { sampleRUM, queue, isSelected } = (window.hlx && window.hlx.rum) ? window.hlx.rum : {};
16
-
17
- const fflags = {
18
- has: (flag) => fflags[flag].indexOf(Array.from(window.origin)
19
- .map((a) => a.charCodeAt(0))
20
- .reduce((a, b) => a + b, 1) % 1371) !== -1,
21
- enabled: (flag, callback) => fflags.has(flag) && callback(),
22
- disabled: (flag, callback) => !fflags.has(flag) && callback(),
23
- onetrust: [543, 770, 1136],
24
- ads: [1139, 543, 770, 984],
25
- email: [1139, 543, 770, 984],
26
- };
27
-
28
- const urlSanitizers = {
29
- full: () => window.location.href,
30
- origin: () => window.location.origin,
31
- path: () => window.location.href.replace(/\?.*$/, ''),
32
- };
33
-
34
- const getTargetValue = (element) => element.getAttribute('data-rum-target') || element.getAttribute('href')
35
- || element.currentSrc || element.getAttribute('src') || element.dataset.action || element.action;
36
-
37
- const targetselector = (element) => {
38
- try {
39
- if (!element) return undefined;
40
- let value = getTargetValue(element);
41
- if (!value && element.tagName !== 'A' && element.closest('a')) {
42
- value = getTargetValue(element.closest('a'));
43
- }
44
- if (value && !value.startsWith('https://')) {
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
- value = new URL(value, window.location).href;
71
+ value = new URL(value, window.location).href;
72
+ }
73
+ return value;
74
+ } catch (error) {
75
+ return null;
47
76
  }
48
- return value;
49
- } catch (error) {
50
- // something went wrong
51
- return null;
52
- }
53
- };
77
+ };
54
78
 
55
- const sourceselector = (element) => {
56
- try {
57
- if (element === document.body || element === document.documentElement || !element) {
58
- return undefined;
59
- }
60
- if (element.getAttribute('data-rum-source')) {
61
- return element.getAttribute('data-rum-source');
62
- }
63
- const form = element.closest('form');
64
- let formElementSelector = '';
65
- if (form && Array.from(form.elements).includes(element)) {
66
- formElementSelector = element.tagName === 'INPUT' ? `form input[type='${element.getAttribute('type')}']` : `form ${element.tagName.toLowerCase()}`;
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
- const blockName = element.closest('.block') ? element.closest('.block').getAttribute('data-block-name') : '';
70
- if (element.id || formElementSelector) {
71
- const id = element.id ? `#${element.id}` : '';
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
- if (element.getAttribute('data-block-name')) {
76
- return `.${element.getAttribute('data-block-name')}`;
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
- if (Array.from(element.classList).some((className) => className.match(/button|cta/))) {
80
- return blockName ? `.${blockName} .button` : '.button';
147
+ function processQueue() {
148
+ while (queue.length) {
149
+ const ck = queue.shift();
150
+ trackCheckpoint(...ck);
81
151
  }
152
+ }
82
153
 
83
- return sourceselector(element.parentElement);
84
- } catch (error) {
85
- return null;
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
- const formSubmitListener = (e) => sampleRUM('formsubmit', { target: targetselector(e.target), source: sourceselector(e.target) });
90
- // eslint-disable-next-line no-use-before-define
91
- const mutationObserver = window.MutationObserver ? new MutationObserver(mutationsCallback) : null;
92
-
93
- // eslint-disable-next-line no-unused-vars
94
- function optedIn(checkpoint, data) {
95
- // TODO: check config service to know if
96
- return true;
97
- }
98
- // Gets configured collection from the config service for the current domain
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
- navigator.sendBeacon(url, body);
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
- function processQueue() {
128
- while (queue.length) {
129
- const ck = queue.shift();
130
- trackCheckpoint(...ck);
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 addCWVTracking() {
135
- setTimeout(() => {
136
- try {
137
- const cwvScript = new URL('.rum/web-vitals/dist/web-vitals.iife.js', sampleRUM.baseURL).href;
138
- if (document.querySelector(`script[src="${cwvScript}"]`)) {
139
- // web vitals script has been loaded already
140
- return;
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
- const script = document.createElement('script');
143
- script.src = cwvScript;
144
- script.onload = () => {
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
- const featureToggle = () => window.location.hostname === 'blog.adobe.com';
157
- const isEager = (metric) => ['CLS', 'LCP'].includes(metric);
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
- new PerformanceObserver((list) => list
193
- .getEntries().map((entry) => navigate(document.referrer, entry.type)))
194
- .observe({ type: 'navigation', buffered: true });
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
- const leave = ((event) => {
197
- try {
198
- if (leave.left || (event.type === 'visibilitychange' && document.visibilityState !== 'hidden')) {
199
- return;
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
- leave.left = true;
202
- sampleRUM('leave');
203
- } catch (error) {
204
- // something went wrong
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 addLoadResourceTracking() {
212
- const observer = new PerformanceObserver((list) => {
213
- try {
214
- list.getEntries()
215
- .filter((entry) => !entry.responseStatus || entry.responseStatus < 400)
216
- .filter((entry) => window.location.hostname === new URL(entry.name).hostname)
217
- .filter((entry) => new URL(entry.name).pathname.match('.*(\\.plain\\.html|\\.json)$'))
218
- .forEach((entry) => {
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 activateBlocksMutationObserver() {
234
- if (!mutationObserver || mutationObserver.active) {
235
- return;
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
- mutationObserver.active = true;
238
- mutationObserver.observe(
239
- document.body,
240
- // eslint-disable-next-line object-curly-newline
241
- { subtree: true, attributes: true, attributeFilter: ['data-block-status'] },
242
- );
243
- }
244
-
245
- function getIntersectionObsever(checkpoint) {
246
- if (!window.IntersectionObserver) {
247
- return null;
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
- activateBlocksMutationObserver();
250
- const observer = new IntersectionObserver((entries) => {
251
- try {
252
- entries
253
- .filter((entry) => entry.isIntersecting)
254
- .forEach((entry) => {
255
- observer.unobserve(entry.target); // observe only once
256
- const target = targetselector(entry.target);
257
- const source = sourceselector(entry.target);
258
- sampleRUM(checkpoint, { target, source });
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
- function addViewMediaTracking(parent) {
275
- const mediaobserver = getIntersectionObsever('viewmedia');
276
- if (mediaobserver) {
277
- parent.querySelectorAll('img, video, audio, iframe').forEach((m) => {
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 addEmailParameterTracking() {
313
- const networks = {
314
- mailchimp: /mc_(c|e)id/,
315
- marketo: /mkt_tok/,
352
+ function addCookieConsentTracking() {
353
+ const cmpCookie = document.cookie.split(';')
354
+ .map((c) => c.trim())
355
+ .find((cookie) => cookie.startsWith('OptanonAlertBoxClosed='));
316
356
 
317
- };
318
- const params = Array.from(new URLSearchParams(window.location.search).keys());
319
- Object.entries(networks).forEach(([network, regex]) => {
320
- params.filter((param) => regex.test(param)).forEach((param) => sampleRUM('email', { source: network, target: param }));
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
- let consentMutationObserver;
343
- const trackShowConsent = () => {
344
- if (document.querySelector('body > div#onetrust-consent-sdk')) {
345
- sampleRUM('consent', { source: 'onetrust', target: 'show' });
346
- if (consentMutationObserver) {
347
- consentMutationObserver.disconnect();
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 true;
350
- }
351
- return false;
352
- };
371
+ return false;
372
+ };
353
373
 
354
- if (!trackShowConsent()) {
355
- // eslint-disable-next-line max-len
356
- consentMutationObserver = window.MutationObserver ? new MutationObserver(trackShowConsent) : null;
357
- if (consentMutationObserver) {
358
- consentMutationObserver.observe(
359
- document.body,
360
- // eslint-disable-next-line object-curly-newline
361
- { attributes: false, childList: true, subtree: false },
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
- function addTrackingFromConfig() {
379
- const trackingFunctions = {
380
- click: () => {
381
- document.addEventListener('click', (event) => {
382
- sampleRUM('click', { target: targetselector(event.target), source: sourceselector(event.target) });
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
- getCollectionConfig().filter((ck) => trackingFunctions[ck])
398
- .forEach((ck) => trackingFunctions[ck]());
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
- function initEnhancer() {
402
- try {
403
- if (sampleRUM) {
404
- addTrackingFromConfig();
405
- window.hlx.rum.collector = trackCheckpoint;
406
- processQueue();
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
- // eslint-disable-next-line padded-blocks
414
- initEnhancer();
433
+ initEnhancer();
434
+ })();