Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@adobe/helix-rum-enhancer

Package Overview
Dependencies
Maintainers
0
Versions
107
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@adobe/helix-rum-enhancer - npm Package Compare versions

Comparing version 2.20.0 to 2.21.0

src/index.md5

15

CHANGELOG.md

@@ -0,1 +1,16 @@

# [2.21.0](https://github.com/adobe/helix-rum-enhancer/compare/v2.20.0...v2.21.0) (2024-09-18)
### Bug Fixes
* **dom:** apply change ([67803e6](https://github.com/adobe/helix-rum-enhancer/commit/67803e60f57ef29b9ea1c51eea26d99c778b5f97))
* **dom:** better handling of empty type form inputs ([d53deb5](https://github.com/adobe/helix-rum-enhancer/commit/d53deb5c6e94ff18df2a01996c39b5cff7636fe4))
* **dom:** better handling of fake blocks ([66a9049](https://github.com/adobe/helix-rum-enhancer/commit/66a90497a45d754ed0fa4e8f8bc0917be64b3245))
### Features
* **checkpoints:** add support for the `language` checkpoint ([f99755a](https://github.com/adobe/helix-rum-enhancer/commit/f99755a5cc8eef83b655a6beff5e7e371196c90d))
* **dom:** more fine-grained sourceselector implementation ([ada279d](https://github.com/adobe/helix-rum-enhancer/commit/ada279d423590fb3feeb304b3a57d6da4ee3c9bf))
# [2.20.0](https://github.com/adobe/helix-rum-enhancer/compare/v2.19.2...v2.20.0) (2024-08-19)

@@ -2,0 +17,0 @@

92

modules/dom.js

@@ -33,2 +33,55 @@ /*

function walk(element, checkFn) {
if (!element || element === document.body || element === document.documentElement) {
return undefined;
}
const checkValue = checkFn(element);
return checkValue || walk(element.parentElement, checkFn);
}
function isDialog(element) {
// doing it well
if (element.tagName === 'DIALOG') return true;
// making the best of it
if (element.getAttribute('role') === 'dialog') return true;
if (element.getAttribute('role') === 'alertdialog') return true;
if (element.getAttribute('aria-modal') === 'true') return true;
// doing it wrong
const computedStyle = window.getComputedStyle(element);
return (computedStyle && computedStyle.position === 'fixed' && computedStyle.zIndex > 100);
}
function isButton(element) {
if (element.tagName === 'BUTTON') return true;
if (element.tagName === 'INPUT' && element.getAttribute('type') === 'button') return true;
if (element.tagName === 'A') {
const classes = Array.from(element.classList);
return classes.some((className) => className.match(/button|cta/));
}
return element.getAttribute('role') === 'button';
}
function getSourceContext(element) {
if (element.closest('form')) return 'form';
const block = element.closest('.block[data-block-name]');
if (block) return `.${block.getAttribute('data-block-name')}`;
if (walk(element, isDialog)) return 'dialog';
if (element.closest('nav')) return 'nav';
if (element.closest('header')) return 'header';
if (element.closest('footer')) return 'footer';
if (element.closest('aside')) return 'aside';
return (walk(element, (e) => e.id && `#${e.id}`));
}
function getSourceElement(element) {
if (element.closest('form') && Array.from(element.closest('form').elements).includes(element)) return element.tagName.toLowerCase() + (element.tagName === 'INPUT' ? `[type='${element.getAttribute('type') || ''}']` : '');
if (walk(element, isButton)) return 'button';
return element.tagName.toLowerCase().match(/^(a|img|video)$/) && element.tagName.toLowerCase();
}
function getSourceIdentifier(element) {
if (element.id) return `#${element.id}`;
if (element.getAttribute('data-block-name')) return `.${element.getAttribute('data-block-name')}`;
return (element.classList.length > 0 && `.${element.classList[0]}`);
}
export const sourceSelector = (element) => {

@@ -42,37 +95,6 @@ try {

}
const form = element.closest('form');
let formElementSelector = '';
if (form && Array.from(form.elements).includes(element)) {
formElementSelector = element.tagName === 'INPUT' ? `form input[type='${element.getAttribute('type')}']` : `form ${element.tagName.toLowerCase()}`;
}
const blockName = element.closest('.block') ? element.closest('.block').getAttribute('data-block-name') : '';
if (element.id || formElementSelector) {
const id = element.id ? `#${element.id}` : '';
return blockName ? `.${blockName} ${formElementSelector}${id}` : `${formElementSelector}${id}`;
}
if (element.getAttribute('data-block-name')) {
return `.${element.getAttribute('data-block-name')}`;
}
const classes = Array.from(element.classList);
const label = element.tagName.toLowerCase();
const firstClass = classes.length > 0 ? `.${classes[0]}` : '';
const labelWithClass = `${element.tagName.toLowerCase()}${firstClass}`;
if (element.tagName.toLowerCase() === 'button'
|| element.type === 'button'
|| classes.some((className) => className.match(/button|cta/))) {
let parent = element.parentElement;
if (!parent) return labelWithClass;
if (parent.id) return `#${parent.id} ${label}`;
while (parent.tagName !== 'BODY' && !parent.id) parent = parent.parentElement;
if (parent.id) return `#${parent.id} ${labelWithClass}`;
return blockName ? `.${blockName} ${labelWithClass}` : labelWithClass;
}
const parent = sourceSelector(element.parentElement);
if (parent) return parent;
return labelWithClass;
const context = getSourceContext(element.parentElement) || '';
const elementName = getSourceElement(element) || '';
const identifier = getSourceIdentifier(element) || '';
return `${context} ${elementName}${identifier}`.trim() || `"${element.textContent.substring(0, 10)}"`;
/* c8 ignore next 3 */

@@ -79,0 +101,0 @@ } catch (error) {

@@ -22,2 +22,3 @@ /*

example: [543, 770, 1136],
language: [543, 959, 1139, 620],
};

@@ -270,2 +270,7 @@ /*

addEmailParameterTracking(sampleRUM);
fflags.enabled('language', () => {
const target = navigator.language;
const source = document.documentElement.lang;
sampleRUM('language', { source, target });
});
}

@@ -272,0 +277,0 @@

{
"name": "@adobe/helix-rum-enhancer",
"version": "2.20.0",
"version": "2.21.0",
"description": "Helix RUM Enhancer",

@@ -36,4 +36,4 @@ "main": "src/index.js",

"devDependencies": {
"@adobe/eslint-config-helix": "2.0.6",
"@adobe/helix-rum-js": "2.2.0",
"@adobe/eslint-config-helix": "2.0.7",
"@adobe/helix-rum-js": "2.4.0",
"@esm-bundle/chai": "4.3.4-fix.0",

@@ -51,11 +51,12 @@ "@semantic-release/changelog": "6.0.3",

"eslint-plugin-header": "3.1.1",
"eslint-plugin-import": "2.29.1",
"husky": "9.1.4",
"jsdoc-to-markdown": "8.0.3",
"eslint-plugin-import": "2.30.0",
"husky": "9.1.5",
"jsdoc-to-markdown": "9.0.0",
"junit-report-builder": "5.0.0",
"lint-staged": "15.2.9",
"lint-staged": "15.2.10",
"mocha": "10.7.3",
"mocha-multi-reporters": "1.5.1",
"rollup": "4.20.0",
"rollup": "4.21.2",
"rollup-plugin-cleanup": "3.2.1",
"rollup-plugin-checksum": "1.0.1",
"rollup-plugin-eslint-bundle": "9.0.0",

@@ -62,0 +63,0 @@ "semantic-release": "24.1.0",

@@ -26,2 +26,45 @@ # Helix RUM Enhancer

### The `source` parameter and the `sourceSelector`
The `source` parameter is a string that can be used to identify the source of the event. It can be used to identify the source of the event, e.g. a button, a link, a form, etc.
It represents an idealized CSS selector that is both human-readable and specific enough to identify the source of the event in the document, even when not having access to the
orginal document. It is idealized because it pretends the DOM would use modern HTML with concise semantics, even if the actual document uses `class` values for things that would
be better represented by semantic HTML elements.
The `sourceSelector` function is a function that takes a DOM element and returns a descriptive `source` parameter. If the element has a `data-rum-source` attribute, its value is used as the `source` parameter. Otherwise, the function tries to generate a `source` parameter based on the element's tag name, class names, and text content.
The structure of the `source` parameter is as follows:
```
<context> <element>#<identifier>
```
All three parts are optional
`context` is
- `form` for form elements
- `dialog` for dialog elements, or parent containers that are fixed positioned and have a positive high z-index
- `.block-name` for Helix blocks
- `header`, `footer`, `nav`, `aside` for main site structure
- `#id` as a fallback, if a container ID is available
`element` is
- `button` for buttons, or links that look like buttons (e.g. with a class `button` or `btn` or `cta`)
- `img` for images
- `video` for videos
- `a` for links that are not buttons
- `input[type="text"]` for input elements (all types are supported)
- `select`, `textarea`, etc. for other form elements
`identifier` is
- the `id` attribute of the element, if provided
- the first `.class` if there are any
- else omitted
Even if an `identifier` is provided, having a `context` and `element` is recommended, as it makes the `source` parameter more readable and easier to understand.
#### Examples
- ``
## Development

@@ -28,0 +71,0 @@

@@ -14,4 +14,6 @@ /*

import cleanup from 'rollup-plugin-cleanup';
import eslint from 'rollup-plugin-eslint-bundle';
import pkg from 'rollup-plugin-checksum';
const checksum = pkg.default;
const banner = `/*

@@ -27,7 +29,4 @@ * Copyright 2024 Adobe. All rights reserved.

* governing permissions and limitations under the License.
*/
*/`;
/* eslint-disable max-classes-per-file, wrap-iife */
// eslint-disable-next-line func-names`;
const bundles = [

@@ -60,11 +59,10 @@ {

cleanup({
comments: ['eslint', 'jsdoc', /^\//, /^\*(?!\sc8\s)(?!\n \* Copyright)/],
maxEmptyLines: -1,
comments: [],
maxEmptyLines: 0,
}),
eslint({
eslintOptions: {
fix: true,
},
checksum({
filename: `${outputFile.split('/').pop()}.md5`,
includeAssets: false,
}),
],
}))];

@@ -12,5 +12,2 @@ /*

*/
/* eslint-disable max-classes-per-file, wrap-iife */
// eslint-disable-next-line func-names
(function () {

@@ -25,6 +22,6 @@ 'use strict';

enabled: (flag, callback) => fflags.has(flag) && callback(),
disabled: (flag, callback) => !fflags.has(flag) && callback(),
eagercwv: [683],
example: [543, 770, 1136],
language: [543, 959, 1139, 620],
};

@@ -37,22 +34,4 @@

const urlSanitizers = {
/**
* Returns the full url.
* If no url is provided, it defaults to window.location.href.
* @param {string} url (default: window.location.href) The url to sanitize
* @returns {string} The sanitized url
*/
full: (url = window.location.href) => new URL(url).toString(),
/**
* Returns the origin of the provided url.
* If no url is provided, it defaults to window.location.href.
* @param {string} url (default: window.location.href) The url to sanitize
* @returns {string} The sanitized url
*/
origin: (url = window.location.href) => new URL(url).origin,
/**
* Returns the sanitized url: the origin and the path (no query params or hash)
* If no url is provided, it defaults to window.location.href.
* @param {string} url (default: window.location.href) The url to sanitize
* @returns {string} The sanitized url
*/
path: (url = window.location.href) => {

@@ -66,3 +45,2 @@ const u = new URL(url);

|| element.currentSrc || element.getAttribute('src') || element.dataset.action || element.action;
const targetSelector = (element) => {

@@ -76,3 +54,2 @@ try {

if (value && !value.startsWith('https://')) {
// resolve relative links
value = new URL(value, window.location).href;

@@ -85,3 +62,47 @@ }

};
function walk(element, checkFn) {
if (!element || element === document.body || element === document.documentElement) {
return undefined;
}
const checkValue = checkFn(element);
return checkValue || walk(element.parentElement, checkFn);
}
function isDialog(element) {
if (element.tagName === 'DIALOG') return true;
if (element.getAttribute('role') === 'dialog') return true;
if (element.getAttribute('role') === 'alertdialog') return true;
if (element.getAttribute('aria-modal') === 'true') return true;
const computedStyle = window.getComputedStyle(element);
return (computedStyle && computedStyle.position === 'fixed' && computedStyle.zIndex > 100);
}
function isButton(element) {
if (element.tagName === 'BUTTON') return true;
if (element.tagName === 'INPUT' && element.getAttribute('type') === 'button') return true;
if (element.tagName === 'A') {
const classes = Array.from(element.classList);
return classes.some((className) => className.match(/button|cta/));
}
return element.getAttribute('role') === 'button';
}
function getSourceContext(element) {
if (element.closest('form')) return 'form';
const block = element.closest('.block[data-block-name]');
if (block) return `.${block.getAttribute('data-block-name')}`;
if (walk(element, isDialog)) return 'dialog';
if (element.closest('nav')) return 'nav';
if (element.closest('header')) return 'header';
if (element.closest('footer')) return 'footer';
if (element.closest('aside')) return 'aside';
return (walk(element, (e) => e.id && `#${e.id}`));
}
function getSourceElement(element) {
if (element.closest('form') && Array.from(element.closest('form').elements).includes(element)) return element.tagName.toLowerCase() + (element.tagName === 'INPUT' ? `[type='${element.getAttribute('type') || ''}']` : '');
if (walk(element, isButton)) return 'button';
return element.tagName.toLowerCase().match(/^(a|img|video)$/) && element.tagName.toLowerCase();
}
function getSourceIdentifier(element) {
if (element.id) return `#${element.id}`;
if (element.getAttribute('data-block-name')) return `.${element.getAttribute('data-block-name')}`;
return (element.classList.length > 0 && `.${element.classList[0]}`);
}
const sourceSelector = (element) => {

@@ -95,37 +116,6 @@ try {

}
const form = element.closest('form');
let formElementSelector = '';
if (form && Array.from(form.elements).includes(element)) {
formElementSelector = element.tagName === 'INPUT' ? `form input[type='${element.getAttribute('type')}']` : `form ${element.tagName.toLowerCase()}`;
}
const blockName = element.closest('.block') ? element.closest('.block').getAttribute('data-block-name') : '';
if (element.id || formElementSelector) {
const id = element.id ? `#${element.id}` : '';
return blockName ? `.${blockName} ${formElementSelector}${id}` : `${formElementSelector}${id}`;
}
if (element.getAttribute('data-block-name')) {
return `.${element.getAttribute('data-block-name')}`;
}
const classes = Array.from(element.classList);
const label = element.tagName.toLowerCase();
const firstClass = classes.length > 0 ? `.${classes[0]}` : '';
const labelWithClass = `${element.tagName.toLowerCase()}${firstClass}`;
if (element.tagName.toLowerCase() === 'button'
|| element.type === 'button'
|| classes.some((className) => className.match(/button|cta/))) {
let parent = element.parentElement;
if (!parent) return labelWithClass;
if (parent.id) return `#${parent.id} ${label}`;
while (parent.tagName !== 'BODY' && !parent.id) parent = parent.parentElement;
if (parent.id) return `#${parent.id} ${labelWithClass}`;
return blockName ? `.${blockName} ${labelWithClass}` : labelWithClass;
}
const parent = sourceSelector(element.parentElement);
if (parent) return parent;
return labelWithClass;
const context = getSourceContext(element.parentElement) || '';
const elementName = getSourceElement(element) || '';
const identifier = getSourceIdentifier(element) || '';
return `${context} ${elementName}${identifier}`.trim() || `"${element.textContent.substring(0, 10)}"`;
} catch (error) {

@@ -140,3 +130,2 @@ return null;

.find((cookie) => cookie.startsWith('OptanonAlertBoxClosed='));
if (cmpCookie) {

@@ -146,3 +135,2 @@ sampleRUM('consent', { source: 'onetrust', target: 'hidden' });

}
let consentMutationObserver;

@@ -159,12 +147,9 @@ const trackShowConsent = () => {

};
if (!trackShowConsent()) {
// eslint-disable-next-line max-len
consentMutationObserver = window.MutationObserver
? new MutationObserver(trackShowConsent)
: null;
: null;
if (consentMutationObserver) {
consentMutationObserver.observe(
document.body,
// eslint-disable-next-line object-curly-newline
{ attributes: false, childList: true, subtree: false },

@@ -175,3 +160,2 @@ );

}
function addUTMParametersTracking(sampleRUM) {

@@ -181,3 +165,2 @@ const usp = new URLSearchParams(window.location.search);

.filter(([key]) => key.startsWith('utm_'))
// exclude keys that may leak PII
.filter(([key]) => key !== 'utm_id')

@@ -215,14 +198,8 @@ .filter(([key]) => key !== 'utm_term')

const { sampleRUM, queue, isSelected } = (window.hlx && window.hlx.rum) ? window.hlx.rum
: {};
: {};
const formSubmitListener = (e) => sampleRUM('formsubmit', { target: targetSelector(e.target), source: sourceSelector(e.target) });
// eslint-disable-next-line no-use-before-define, max-len
const blocksMutationObserver = window.MutationObserver ? new MutationObserver(blocksMutationsCallback)
: {};
// eslint-disable-next-line no-use-before-define, max-len
: {};
const mediaMutationObserver = window.MutationObserver ? new MutationObserver(mediaMutationsCallback)
: {};
: {};
function trackCheckpoint(checkpoint, data, t) {

@@ -232,3 +209,2 @@ const { weight, id } = window.hlx.rum;

const sendPing = (pdata = data) => {
// eslint-disable-next-line object-curly-newline, max-len
const body = JSON.stringify({ weight, id, referer: urlSanitizers[window.hlx.RUM_MASK_URL || 'path'](), checkpoint, t, ...data }, KNOWN_PROPERTIES);

@@ -242,3 +218,2 @@ const { href: url, origin } = new URL(`.rum/${weight}`, sampleRUM.collectBaseURL || sampleRUM.baseURL);

}
// eslint-disable-next-line no-console
console.debug(`ping:${checkpoint}`, pdata);

@@ -249,3 +224,2 @@ };

}
function processQueue() {

@@ -257,3 +231,2 @@ while (queue && queue.length) {

}
function addCWVTracking() {

@@ -264,3 +237,2 @@ setTimeout(() => {

if (document.querySelector(`script[src="${cwvScript}"]`)) {
// web vitals script has been loaded already
return;

@@ -281,7 +253,3 @@ }

};
const isEager = (metric) => ['CLS', 'LCP'].includes(metric);
// When loading `web-vitals` using a classic script, all the public
// methods can be found on the `webVitals` global namespace.
['FID', 'INP', 'TTFB', 'CLS', 'LCP'].forEach((metric) => {

@@ -300,23 +268,18 @@ const metricFn = window.webVitals[`on${metric}`];

} catch (error) {
// something went wrong
}
}, 2000); // wait for delayed
}, 2000);
}
function addNavigationTracking() {
// enter checkpoint when referrer is not the current page url
const navigate = (source, type) => {
const payload = { source, target: document.visibilityState };
// reload: same page, navigate: same origin, enter: everything else
if (type === 'reload' || source === window.location.href) {
sampleRUM('reload', payload);
} else if (type && type !== 'navigate') {
sampleRUM(type, payload); // back, forward, prerender, etc.
sampleRUM(type, payload);
} else if (source && window.location.origin === new URL(source).origin) {
sampleRUM('navigate', payload); // internal navigation
sampleRUM('navigate', payload);
} else {
sampleRUM('enter', payload); // enter site
sampleRUM('enter', payload);
}
};
new PerformanceObserver((list) => list

@@ -326,3 +289,2 @@ .getEntries().map((entry) => navigate(window.hlx.referrer || document.referrer, entry.type)))

}
function addLoadResourceTracking() {

@@ -344,3 +306,2 @@ const observer = new PerformanceObserver((list) => {

} catch (error) {
// something went wrong
}

@@ -350,3 +311,2 @@ });

}
function activateBlocksMutationObserver() {

@@ -359,7 +319,5 @@ if (!blocksMutationObserver || blocksMutationObserver.active) {

document.body,
// eslint-disable-next-line object-curly-newline
{ subtree: true, attributes: true, attributeFilter: ['data-block-status'] },
);
}
function activateMediaMutationObserver() {

@@ -372,7 +330,5 @@ if (!mediaMutationObserver || mediaMutationObserver.active) {

document.body,
// eslint-disable-next-line object-curly-newline
{ subtree: true, attributes: false, childList: true },
);
}
function getIntersectionObsever(checkpoint) {

@@ -389,3 +345,3 @@ if (!window.IntersectionObserver) {

.forEach((entry) => {
observer.unobserve(entry.target); // observe only once
observer.unobserve(entry.target);
const target = targetSelector(entry.target);

@@ -396,3 +352,2 @@ const source = sourceSelector(entry.target);

} catch (error) {
// something went wrong
}

@@ -409,3 +364,2 @@ });

}
const observedMedia = new Set();

@@ -423,3 +377,2 @@ function addViewMediaTracking(parent) {

}
function addFormTracking(parent) {

@@ -429,13 +382,10 @@ activateBlocksMutationObserver();

parent.querySelectorAll('form').forEach((form) => {
form.removeEventListener('submit', formSubmitListener); // listen only once
form.removeEventListener('submit', formSubmitListener);
form.addEventListener('submit', formSubmitListener);
});
}
function addObserver(ck, fn, block) {
return DEFAULT_TRACKING_EVENTS.includes(ck) && fn(block);
}
function blocksMutationsCallback(mutations) {
// block specific mutations
mutations

@@ -449,5 +399,3 @@ .filter((m) => m.type === 'attributes' && m.attributeName === 'data-block-status')

}
function mediaMutationsCallback(mutations) {
// media mutations
mutations

@@ -458,3 +406,2 @@ .forEach((m) => {

}
function addTrackingFromConfig() {

@@ -474,4 +421,8 @@ document.addEventListener('click', (event) => {

addEmailParameterTracking(sampleRUM);
fflags.enabled('language', () => {
const target = navigator.language;
const source = document.documentElement.lang;
sampleRUM('language', { source, target });
});
}
function initEnhancer() {

@@ -485,7 +436,6 @@ try {

} catch (error) {
// something went wrong
}
}
initEnhancer();
initEnhancer();
})();

@@ -46,3 +46,3 @@ /*

context.body = context.body
.replace(/const weight.*/, 'const weight = 1;')
.replace(/const weight.*/, 'const weight = 1')
.replace(/navigator\.sendBeacon/g, 'fakeSendBeacon')

@@ -49,0 +49,0 @@ .replace('.rum/@adobe/helix-rum-enhancer@^2/src/index.js', 'src/index.map.js');

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc