@adobe/helix-rum-enhancer 2.47.3 → 2.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -23,7 +23,7 @@
23
23
  a11y: [557, 781, 897, 955, 959]
24
24
  };
25
25
 
26
- const KNOWN_PROPERTIES = ['weight', 'id', 'referer', 'checkpoint', 't', 'source', 'target', 'cwv', 'CLS', 'FID', 'LCP', 'INP', 'TTFB'];
26
+ const KNOWN_PROPERTIES = ['weight', 'id', 'referer', 'checkpoint', 't', 'source', 'target', 'cwv', 'CLS', 'FID', 'LCP', 'INP', 'TTFB', 'ua'];
27
27
  const DEFAULT_TRACKING_EVENTS = ['click', 'cwv', 'form', 'viewblock', 'viewmedia', 'loadresource', 'utm', 'paid', 'email', 'consent'];
28
28
  fflags.enabled('example', () => DEFAULT_TRACKING_EVENTS.push('example'));
29
29
 
@@ -57,11 +57,20 @@
57
57
  return null;
58
58
  }
59
59
  };
60
- function walk(el, checkFn) {
60
+ const untrustedClickPayload = event => {
61
+ if (event && event.isTrusted === false) {
62
+ const ua = navigator.userAgent;
63
+ return {
64
+ ua: ua.includes('+http') ? ua : `${ua} +http://event.untrusted`
65
+ };
66
+ }
67
+ return {};
68
+ };
69
+ function walk(el, check) {
61
70
  if (!el || el === document.body || el === document.documentElement) {
62
71
  return undefined;
63
72
  }
64
- return checkFn(el) || walk(el.parentElement || el.parentNode && el.parentNode.host, checkFn);
73
+ return check(el) || walk(el.parentElement || el.parentNode && el.parentNode.host, check);
65
74
  }
66
75
  function isDialog(el) {
67
76
  const cs = window.getComputedStyle(el);
@@ -72,30 +81,30 @@
72
81
  return true;
73
82
  }
74
83
  if (el.tagName === 'A') {
75
- return Array.from(el.classList).some(className => className.match(/button|cta/));
84
+ return Array.from(el.classList).some(cls => cls.match(/button|cta/));
76
85
  }
77
86
  return el.getAttribute('role') === 'button';
78
87
  }
79
- function getSourceContext(el) {
80
- const formEl = el.closest('form');
81
- if (formEl) {
82
- const id = formEl.getAttribute('id');
88
+ function srcContext(el) {
89
+ const form = el.closest('form');
90
+ if (form) {
91
+ const id = form.getAttribute('id');
83
92
  if (id) {
84
93
  return `form#${CSS.escape(id)}`;
85
94
  }
86
- return `form${formEl.classList.length > 0 ? `.${CSS.escape(formEl.classList[0])}` : ''}`;
95
+ return `form${form.classList.length > 0 ? `.${CSS.escape(form.classList[0])}` : ''}`;
87
96
  }
88
97
  const block = el.closest('.block[data-block-name]');
89
98
  return block && `.${block.getAttribute('data-block-name')}` || walk(el, isDialog) && 'dialog' || walk(el, e => e.tagName && e.tagName.includes('-') && e.tagName.toLowerCase()) || ['nav', 'header', 'footer', 'aside'].find(t => el.closest(t)) || walk(el, e => e.id && `#${CSS.escape(e.id)}`);
90
99
  }
91
- function getSourceElement(el) {
100
+ function srcElement(el) {
92
101
  const f = el.closest('form');
93
102
  if (f && Array.from(f.elements).includes(el)) {
94
103
  return el.tagName.toLowerCase() + (['INPUT', 'BUTTON'].includes(el.tagName) ? `[type='${el.getAttribute('type') || ''}']` : '');
95
104
  }
96
105
  return walk(el, isButton) && 'button' || el.tagName.toLowerCase().match(/^(a|img|video|form)$/) && el.tagName.toLowerCase();
97
106
  }
98
- function getSourceIdentifier(el) {
107
+ function srcId(el) {
99
108
  return el.id && `#${CSS.escape(el.id)}` || el.getAttribute('data-block-name') && `.${el.getAttribute('data-block-name')}` || el.classList.length > 0 && `.${CSS.escape(el.classList[0])}`;
100
109
  }
101
110
  const sourceSelector = el => {
@@ -106,9 +115,9 @@
106
115
  if (el.getAttribute('data-rum-source')) {
107
116
  return el.getAttribute('data-rum-source');
108
117
  }
109
- const ctx = getSourceContext(el.parentElement) || '';
110
- const name = getSourceElement(el) || '';
111
- const id = getSourceIdentifier(el) || '';
118
+ const ctx = srcContext(el.parentElement) || '';
119
+ const name = srcElement(el) || '';
120
+ const id = srcId(el) || '';
112
121
  return `${ctx} ${name}${id}`.trim() || `"${el.textContent.substring(0, 10)}"`;
113
122
  } catch (error) {
114
123
  return null;
@@ -122,19 +131,19 @@
122
131
  } = window.hlx && window.hlx.rum ? window.hlx.rum : {};
123
132
  const createMO = cb => window.MutationObserver ? new MutationObserver(cb) : {};
124
133
  const [blocksMO, mediaMO] = [blocksMCB, mediaMCB].map(createMO);
125
- const hasCookieKey = key => document.cookie.split(';').some(c => c.trim().startsWith(`${key}=`));
126
- const pluginBasePath = new URL(document.currentScript.src).href.replace(/index(\.map)?\.js/, 'plugins');
134
+ const hasCookie = key => document.cookie.split(';').some(c => c.trim().startsWith(`${key}=`));
135
+ const pluginBase = new URL(document.currentScript.src).href.replace(/index(\.map)?\.js/, 'plugins');
127
136
  const CONSENT_PROVIDERS = [{
128
137
  name: 'onetrust',
129
- detect: () => hasCookieKey('OptanonAlertBoxClosed') || document.querySelector('#onetrust-banner-sdk, #onetrust-pc-sdk')
138
+ detect: () => hasCookie('OptanonAlertBoxClosed') || document.querySelector('#onetrust-banner-sdk, #onetrust-pc-sdk')
130
139
  }, {
131
140
  name: 'trustarc',
132
- detect: () => ['notice_gdpr_prefs', 'notice_preferences'].some(hasCookieKey) || document.querySelector('#truste-consent-track')
141
+ detect: () => ['notice_gdpr_prefs', 'notice_preferences'].some(hasCookie) || document.querySelector('#truste-consent-track')
133
142
  }, {
134
143
  name: 'usercentrics',
135
144
  detect: () => window.localStorage.getItem('uc_gcm') || document.querySelector('#usercentrics-root')
136
145
  }];
137
- const getConsentProvider = () => CONSENT_PROVIDERS.find(({
146
+ const getConsent = () => CONSENT_PROVIDERS.find(({
138
147
  detect
139
148
  }) => detect());
140
149
  const bodyChildMO = {
@@ -146,41 +155,41 @@
146
155
  }
147
156
  };
148
157
  const PLUGINS = {
149
- cwv: `${pluginBasePath}/cwv.js`,
150
- a11y: `${pluginBasePath}/a11y.js`,
158
+ cwv: `${pluginBase}/cwv.js`,
159
+ a11y: `${pluginBase}/a11y.js`,
151
160
  form: {
152
- url: `${pluginBasePath}/form.js`,
161
+ url: `${pluginBase}/form.js`,
153
162
  when: () => document.querySelector('form'),
154
163
  isBlockDependent: true,
155
- mutationObserverParams: bodyChildMO
164
+ moParams: bodyChildMO
156
165
  },
157
166
  redirect: {
158
- url: `${pluginBasePath}/redirect.js`,
167
+ url: `${pluginBase}/redirect.js`,
159
168
  when: ({
160
169
  perfEntry: pe,
161
170
  urlParameters: usp
162
171
  }) => pe && (usp.get('redirect_from') || pe.redirectCount > 0 || pe.fetchStart > 50)
163
172
  },
164
173
  video: {
165
- url: `${pluginBasePath}/video.js`,
174
+ url: `${pluginBase}/video.js`,
166
175
  when: () => document.querySelector('video'),
167
176
  isBlockDependent: true
168
177
  },
169
178
  webcomponent: {
170
- url: `${pluginBasePath}/webcomponent.js`,
179
+ url: `${pluginBase}/webcomponent.js`,
171
180
  when: () => [...document.querySelectorAll('*')].some(el => el.tagName && el.tagName.includes('-')),
172
181
  isBlockDependent: true
173
182
  },
174
183
  martech: {
175
- url: `${pluginBasePath}/martech.js`,
184
+ url: `${pluginBase}/martech.js`,
176
185
  when: ({
177
186
  urlParameters
178
187
  }) => urlParameters.size > 0
179
188
  },
180
189
  consent: {
181
- when: () => getConsentProvider(),
190
+ when: () => getConsent(),
182
191
  isBlockDependent: true,
183
- mutationObserverParams: bodyChildMO
192
+ moParams: bodyChildMO
184
193
  }
185
194
  };
186
195
  const MAX_EV = 1023;
@@ -189,10 +198,10 @@
189
198
  if (!window.IntersectionObserver) {
190
199
  return null;
191
200
  }
192
- const observer = new IntersectionObserver(entries => {
201
+ const obs = new IntersectionObserver(entries => {
193
202
  try {
194
203
  entries.filter(e => e.isIntersecting).forEach(e => {
195
- observer.unobserve(e.target);
204
+ obs.unobserve(e.target);
196
205
  const target = targetSelector(e.target);
197
206
  const source = sourceSelector(e.target);
198
207
  if (checkpoint === 'viewmedia') {
@@ -211,14 +220,15 @@
211
220
  });
212
221
  } catch (error) {}
213
222
  });
214
- return observer;
223
+ return obs;
215
224
  }
216
- const PLUGIN_PARAMETERS = {
225
+ const PLUGIN_PARAMS = {
217
226
  context: document.body,
218
227
  fflags,
219
228
  sampleRUM,
220
229
  sourceSelector,
221
230
  targetSelector,
231
+ untrustedClickPayload,
222
232
  getIntersectionObserver,
223
233
  createMO
224
234
  };
@@ -230,36 +240,36 @@
230
240
  ...params,
231
241
  urlParameters: usp
232
242
  })) {
233
- if (plugin.mutationObserverParams && !plugin.isBeingObserved) {
243
+ if (plugin.moParams && !plugin.isBeingObserved) {
234
244
  createPluginMO(key, params, usp);
235
245
  }
236
246
  return null;
237
247
  }
238
248
  if (key === 'consent') {
239
- plugin.url = `${pluginBasePath}/${getConsentProvider().name}.js`;
249
+ plugin.url = `${pluginBase}/${getConsent().name}.js`;
240
250
  }
241
251
  if (!pluginCache.has(key)) {
242
252
  pluginCache.set(key, import(`${plugin.url || plugin}`));
243
253
  }
244
254
  return pluginCache.get(key).then(p => p.default && p.default(params) || typeof p === 'function' && p(params)).catch(() => {});
245
255
  }
246
- function loadPlugins(filter = () => true, params = PLUGIN_PARAMETERS) {
256
+ function loadPlugins(filter = () => true, params = PLUGIN_PARAMS) {
247
257
  Object.entries(PLUGINS).filter(([, plugin]) => filter(plugin)).map(([key]) => loadPlugin(key, params));
248
258
  }
249
259
  function createPluginMO(key, params, usp) {
250
260
  const plugin = PLUGINS[key];
251
- const observer = createMO(() => {
261
+ const obs = createMO(() => {
252
262
  if (plugin.when({
253
263
  urlParameters: usp
254
264
  })) {
255
265
  plugin.isBeingObserved = false;
256
- observer.disconnect();
266
+ obs.disconnect();
257
267
  loadPlugin(key, params);
258
268
  }
259
269
  });
260
- if (observer instanceof MutationObserver) {
270
+ if (obs instanceof MutationObserver) {
261
271
  plugin.isBeingObserved = true;
262
- observer.observe(plugin.mutationObserverParams.target, plugin.mutationObserverParams.options);
272
+ obs.observe(plugin.moParams.target, plugin.moParams.options);
263
273
  }
264
274
  }
265
275
  let maxEvents = MAX_EV;
@@ -326,7 +336,7 @@
326
336
  sampleRUM('enter', payload);
327
337
  }
328
338
  loadPlugin('redirect', {
329
- ...PLUGIN_PARAMETERS,
339
+ ...PLUGIN_PARAMS,
330
340
  perfEntry
331
341
  });
332
342
  };
@@ -339,7 +349,7 @@
339
349
  });
340
350
  }
341
351
  function addLoadResourceTracking() {
342
- const observer = new PerformanceObserver(list => {
352
+ const obs = new PerformanceObserver(list => {
343
353
  try {
344
354
  const entries = list.getEntries();
345
355
  entries.filter(e => !e.responseStatus || e.responseStatus < 400).filter(e => window.location.hostname === new URL(e.name).hostname || fflags.has('allresources')).filter(e => {
@@ -347,10 +357,10 @@
347
357
  pathname,
348
358
  hostname
349
359
  } = new URL(e.name);
350
- const extensionMatch = pathname.match(hostname !== window.location.hostname ? '.*(\\.html$|\\.json|\\.js|graphql|api)' : '.*(\\.plain\\.html$|\\.json|graphql|api)');
360
+ const extMatch = pathname.match(hostname !== window.location.hostname ? '.*(\\.html$|\\.json|\\.js|graphql|api)' : '.*(\\.plain\\.html$|\\.json|graphql|api)');
351
361
  const isDropIn = fflags.has('allresources') && (pathname.includes('__dropins__/storefront-') || pathname.includes('scripts/dropins/storefront-'));
352
362
  const isImage = fflags.has('allresources') && pathname.match(/\.(png|jpe?g|svg)$/i);
353
- return extensionMatch || isDropIn || isImage;
363
+ return extMatch || isDropIn || isImage;
354
364
  }).forEach(e => {
355
365
  sampleRUM('loadresource', {
356
366
  source: e.name,
@@ -365,7 +375,7 @@
365
375
  });
366
376
  } catch (error) {}
367
377
  });
368
- observer.observe({
378
+ obs.observe({
369
379
  type: 'resource',
370
380
  buffered: true
371
381
  });
@@ -392,21 +402,21 @@
392
402
  childList: true
393
403
  });
394
404
  }
395
- function addViewBlockTracking(element) {
396
- const blockobserver = getIntersectionObserver('viewblock');
397
- if (blockobserver) {
398
- const blocks = element.getAttribute('data-block-status') ? [element] : element.querySelectorAll('div[data-block-status="loaded"]');
399
- blocks.forEach(b => blockobserver.observe(b));
405
+ function addViewBlockTracking(el) {
406
+ const blockObs = getIntersectionObserver('viewblock');
407
+ if (blockObs) {
408
+ const blocks = el.getAttribute('data-block-status') ? [el] : el.querySelectorAll('div[data-block-status="loaded"]');
409
+ blocks.forEach(b => blockObs.observe(b));
400
410
  }
401
411
  }
402
412
  const observedMedia = new Set();
403
413
  function addViewMediaTracking(parent) {
404
- const mediaobserver = getIntersectionObserver('viewmedia');
405
- if (mediaobserver) {
414
+ const mediaObs = getIntersectionObserver('viewmedia');
415
+ if (mediaObs) {
406
416
  parent.querySelectorAll('img, video, audio, iframe').forEach(m => {
407
417
  if (!observedMedia.has(m)) {
408
418
  observedMedia.add(m);
409
- mediaobserver.observe(m);
419
+ mediaObs.observe(m);
410
420
  }
411
421
  });
412
422
  }
@@ -417,7 +427,7 @@
417
427
  function blocksMCB(mutations) {
418
428
  mutations.filter(m => m.type === 'attributes' && m.attributeName === 'data-block-status').filter(m => m.target.dataset.blockStatus === 'loaded').forEach(m => {
419
429
  addObserver('form', el => loadPlugins(p => p.isBlockDependent, {
420
- ...PLUGIN_PARAMETERS,
430
+ ...PLUGIN_PARAMS,
421
431
  context: el
422
432
  }), m.target);
423
433
  addObserver('viewblock', addViewBlockTracking, m.target);
@@ -437,7 +447,8 @@
437
447
  }
438
448
  sampleRUM('click', {
439
449
  target: targetSelector(event.target),
440
- source: sourceSelector(event.target)
450
+ source: sourceSelector(event.target),
451
+ ...untrustedClickPayload(event)
441
452
  });
442
453
  });
443
454
  addNavigationTracking();
package/src/index.md5 CHANGED
@@ -1 +1 @@
1
- fa307b6aa9f67e9f3e13a064b9d0d9d1
1
+ 7b41a36b2c1fe6f67ae750a38cc144db
package/src/index.sri CHANGED
@@ -1 +1 @@
1
- sha384-LDEOuwnv1QygFu3bCEAHTg6z4MeF1qxPVmZFOdVyUNmMbptaV4/+WwXyADbV1Uom
1
+ sha384-Bj6YB2m5ENO7H/0f3agpgLqday7DeSX2gyCAHRh0eRjrkzsEKXSIWq6eadByRqut
@@ -47,7 +47,8 @@
47
47
  },
48
48
  a = new WeakMap();
49
49
  function d(e, t) {
50
- return a.get(e) || a.set(e, new t()), a.get(e);
50
+ let n = a.get(t);
51
+ return n || (n = new WeakMap(), a.set(t, n)), n.get(e) || n.set(e, new t()), n.get(e);
51
52
  }
52
53
  class h {
53
54
  t;
@@ -85,17 +86,17 @@
85
86
  let u = -1;
86
87
  const m = new Set(),
87
88
  g = () => "hidden" !== document.visibilityState || document.prerendering ? 1 / 0 : 0,
88
- v = e => {
89
+ p = e => {
89
90
  if ("hidden" === document.visibilityState) {
90
91
  if ("visibilitychange" === e.type) for (const e of m) e();
91
- isFinite(u) || (u = "visibilitychange" === e.type ? e.timeStamp : 0, removeEventListener("prerenderingchange", v, true));
92
+ isFinite(u) || (u = "visibilitychange" === e.type ? e.timeStamp : 0, removeEventListener("prerenderingchange", p, true));
92
93
  }
93
94
  },
94
- p = () => {
95
+ v = () => {
95
96
  if (u < 0) {
96
97
  const e = r(),
97
98
  t = document.prerendering ? void 0 : globalThis.performance.getEntriesByType("visibility-state").find(t => "hidden" === t.name && t.startTime >= e)?.startTime;
98
- u = t ?? g(), addEventListener("visibilitychange", v, true), addEventListener("prerenderingchange", v, true), n(() => {
99
+ u = t ?? g(), addEventListener("visibilitychange", p, true), addEventListener("prerenderingchange", p, true), n(() => {
99
100
  setTimeout(() => {
100
101
  u = g();
101
102
  });
@@ -116,7 +117,7 @@
116
117
  T = [1800, 3e3],
117
118
  b = (e, t = {}) => {
118
119
  y(() => {
119
- const o = p();
120
+ const o = v();
120
121
  let a,
121
122
  d = c("FCP");
122
123
  const h = f("paint", e => {
@@ -148,8 +149,8 @@
148
149
  l = [];
149
150
  u = new Map();
150
151
  m;
151
- v;
152
- p() {
152
+ p;
153
+ v() {
153
154
  F = C(), this.l.length = 0, this.u.clear();
154
155
  }
155
156
  T() {
@@ -169,7 +170,7 @@
169
170
  const e = this.l.splice(10);
170
171
  for (const t of e) this.u.delete(t.id);
171
172
  }
172
- this.v?.(n);
173
+ this.p?.(n);
173
174
  }
174
175
  }
175
176
  }
@@ -205,7 +206,7 @@
205
206
  document.prerendering ? y(() => H(e)) : "complete" !== document.readyState ? addEventListener("load", () => H(e), true) : setTimeout(e);
206
207
  };
207
208
  return e.CLSThresholds = E, e.FCPThresholds = T, e.INPThresholds = B, e.LCPThresholds = q, e.TTFBThresholds = N, e.onCLS = (e, t = {}) => {
208
- const o = p();
209
+ const o = v();
209
210
  b(l(() => {
210
211
  let r,
211
212
  a = c("CLS", 0);
@@ -223,7 +224,7 @@
223
224
  }));
224
225
  }, e.onFCP = b, e.onINP = (e, t = {}) => {
225
226
  if (!globalThis.PerformanceEventTiming || !("interactionId" in PerformanceEventTiming.prototype)) return;
226
- const s = p();
227
+ const s = v();
227
228
  y(() => {
228
229
  I();
229
230
  let o,
@@ -245,12 +246,12 @@
245
246
  }), s.onHidden(() => {
246
247
  h(l.takeRecords()), o(true);
247
248
  }), n(() => {
248
- a.p(), r = c("INP"), o = i(e, r, B, t.reportAllChanges);
249
+ a.v(), r = c("INP"), o = i(e, r, B, t.reportAllChanges);
249
250
  }));
250
251
  });
251
252
  }, e.onLCP = (e, t = {}) => {
252
253
  y(() => {
253
- const o = p();
254
+ const o = v();
254
255
  let a,
255
256
  h = c("LCP");
256
257
  const u = d(t, S),
@@ -318,6 +319,13 @@ function addCWVTracking({
318
319
  data.target = targetSelector(element);
319
320
  data.source = sourceSelector(element) || element && element.outerHTML.slice(0, 30);
320
321
  }
322
+ if (measurement.name === 'CLS' && measurement.entries.length > 0) {
323
+ const largest = measurement.entries.reduce((a, b) => b.value > (a ? a.value : 0) ? b : a, undefined);
324
+ const node = largest && largest.sources && largest.sources.map(s => s.node).find(n => n && n.nodeType === 1);
325
+ if (node) {
326
+ data.source = sourceSelector(node) || node.outerHTML.slice(0, 30);
327
+ }
328
+ }
321
329
  sampleRUM('cwv', data);
322
330
  };
323
331
  const isEager = metric => ['CLS', 'LCP'].includes(metric);