@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.
@@ -1,416 +0,0 @@
1
- /*
2
- * Copyright 2024 Adobe. All rights reserved.
3
- * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License. You may obtain a copy
5
- * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
- *
7
- * Unless required by applicable law or agreed to in writing, software distributed under
8
- * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
- * OF ANY KIND, either express or implied. See the License for the specific language
10
- * governing permissions and limitations under the License.
11
- */
12
-
13
- /* eslint-disable max-classes-per-file, wrap-iife */
14
- // eslint-disable-next-line func-names
15
- (function () {
16
- 'use strict';
17
-
18
- /* eslint-env browser */
19
- const KNOWN_PROPERTIES = ['weight', 'id', 'referer', 'checkpoint', 't', 'source', 'target', 'cwv', 'CLS', 'FID', 'LCP', 'INP', 'TTFB'];
20
- const DEFAULT_TRACKING_EVENTS = ['click', 'cwv', 'form', 'enterleave', 'viewblock', 'viewmedia', 'loadresource', 'utm'];
21
- const { sampleRUM, queue, isSelected } = (window.hlx && window.hlx.rum) ? window.hlx.rum : {};
22
-
23
- const fflags = {
24
- has: (flag) => fflags[flag].indexOf(Array.from(window.origin)
25
- .map((a) => a.charCodeAt(0))
26
- .reduce((a, b) => a + b, 1) % 1371) !== -1,
27
- enabled: (flag, callback) => fflags.has(flag) && callback(),
28
- disabled: (flag, callback) => !fflags.has(flag) && callback(),
29
- onetrust: [543, 770, 1136],
30
- ads: [1139, 543, 770, 984],
31
- email: [1139, 543, 770, 984],
32
- };
33
-
34
- const urlSanitizers = {
35
- full: () => window.location.href,
36
- origin: () => window.location.origin,
37
- path: () => window.location.href.replace(/\?.*$/, ''),
38
- };
39
-
40
- const getTargetValue = (element) => element.getAttribute('data-rum-target') || element.getAttribute('href')
41
- || element.currentSrc || element.getAttribute('src') || element.dataset.action || element.action;
42
-
43
- const targetselector = (element) => {
44
- try {
45
- if (!element) return undefined;
46
- let value = getTargetValue(element);
47
- if (!value && element.tagName !== 'A' && element.closest('a')) {
48
- value = getTargetValue(element.closest('a'));
49
- }
50
- if (value && !value.startsWith('https://')) {
51
- // resolve relative links
52
- value = new URL(value, window.location).href;
53
- }
54
- return value;
55
- } catch (error) {
56
- // something went wrong
57
- return null;
58
- }
59
- };
60
-
61
- const sourceselector = (element) => {
62
- try {
63
- if (element === document.body || element === document.documentElement || !element) {
64
- return undefined;
65
- }
66
- if (element.getAttribute('data-rum-source')) {
67
- return element.getAttribute('data-rum-source');
68
- }
69
- const form = element.closest('form');
70
- let formElementSelector = '';
71
- if (form && Array.from(form.elements).includes(element)) {
72
- formElementSelector = element.tagName === 'INPUT' ? `form input[type='${element.getAttribute('type')}']` : `form ${element.tagName.toLowerCase()}`;
73
- }
74
-
75
- const blockName = element.closest('.block') ? element.closest('.block').getAttribute('data-block-name') : '';
76
- if (element.id || formElementSelector) {
77
- const id = element.id ? `#${element.id}` : '';
78
- return blockName ? `.${blockName} ${formElementSelector}${id}` : `${formElementSelector}${id}`;
79
- }
80
-
81
- if (element.getAttribute('data-block-name')) {
82
- return `.${element.getAttribute('data-block-name')}`;
83
- }
84
-
85
- if (Array.from(element.classList).some((className) => className.match(/button|cta/))) {
86
- return blockName ? `.${blockName} .button` : '.button';
87
- }
88
-
89
- return sourceselector(element.parentElement);
90
- } catch (error) {
91
- return null;
92
- }
93
- };
94
-
95
- const formSubmitListener = (e) => sampleRUM('formsubmit', { target: targetselector(e.target), source: sourceselector(e.target) });
96
- // eslint-disable-next-line no-use-before-define
97
- const mutationObserver = window.MutationObserver ? new MutationObserver(mutationsCallback) : null;
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 (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));
117
- } else {
118
- navigator.sendBeacon(url, body);
119
- }
120
- // eslint-disable-next-line no-console
121
- console.debug(`ping:${checkpoint}`, pdata);
122
- };
123
- sendPing(data);
124
- }
125
- }
126
-
127
- function processQueue() {
128
- while (queue.length) {
129
- const ck = queue.shift();
130
- trackCheckpoint(...ck);
131
- }
132
- }
133
-
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;
141
- }
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
- };
155
-
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
189
- }
190
- };
191
-
192
- new PerformanceObserver((list) => list
193
- .getEntries().map((entry) => navigate(document.referrer, entry.type)))
194
- .observe({ type: 'navigation', buffered: true });
195
-
196
- const leave = ((event) => {
197
- try {
198
- if (leave.left || (event.type === 'visibilitychange' && document.visibilityState !== 'hidden')) {
199
- return;
200
- }
201
- leave.left = true;
202
- sampleRUM('leave');
203
- } catch (error) {
204
- // something went wrong
205
- }
206
- });
207
- window.addEventListener('visibilitychange', ((event) => leave(event)));
208
- window.addEventListener('pagehide', ((event) => leave(event)));
209
- }
210
-
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
228
- }
229
- });
230
- observer.observe({ type: 'resource', buffered: true });
231
- }
232
-
233
- function activateBlocksMutationObserver() {
234
- if (!mutationObserver || mutationObserver.active) {
235
- return;
236
- }
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;
248
- }
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));
271
- }
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
- }
281
- });
282
- }
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
-
312
- function addEmailParameterTracking() {
313
- const networks = {
314
- mailchimp: /mc_(c|e)id/,
315
- marketo: /mkt_tok/,
316
-
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
- }
341
-
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();
348
- }
349
- return true;
350
- }
351
- return false;
352
- };
353
-
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
- );
363
- }
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
-
378
- function addTrackingFromConfig() {
379
- const trackingFunctions = {
380
- click: () => {
381
- document.addEventListener('click', (event) => {
382
- sampleRUM('click', { target: targetselector(event.target), source: sourceselector(event.target) });
383
- });
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
-
397
- getCollectionConfig().filter((ck) => trackingFunctions[ck])
398
- .forEach((ck) => trackingFunctions[ck]());
399
- }
400
-
401
- function initEnhancer() {
402
- try {
403
- if (sampleRUM) {
404
- addTrackingFromConfig();
405
- window.hlx.rum.collector = trackCheckpoint;
406
- processQueue();
407
- }
408
- } catch (error) {
409
- // something went wrong
410
- }
411
- }
412
-
413
- // eslint-disable-next-line padded-blocks
414
- initEnhancer();
415
-
416
- })();