@adobe/helix-rum-enhancer 2.20.0 → 2.21.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/.releaserc.cjs +12 -1
- package/CHANGELOG.md +15 -0
- package/README.md +43 -0
- package/modules/dom.js +57 -35
- package/modules/fflags.js +1 -0
- package/modules/index.js +5 -0
- package/package.json +9 -8
- package/rollup.config.js +9 -11
- package/src/index.js +66 -116
- package/src/index.md5 +1 -0
- package/web-test-runner.config.js +1 -1
package/.releaserc.cjs
CHANGED
|
@@ -10,7 +10,18 @@ module.exports = {
|
|
|
10
10
|
"assets": ["package.json", "CHANGELOG.md"],
|
|
11
11
|
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
12
12
|
}],
|
|
13
|
-
["@semantic-release/github", {
|
|
13
|
+
["@semantic-release/github", {
|
|
14
|
+
"assets": [
|
|
15
|
+
{
|
|
16
|
+
"path": "src/index.js",
|
|
17
|
+
"label": "RUM Enhancer JS"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"path": "src/index.md5",
|
|
21
|
+
"label": "RUM Enhancer Hash"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}]
|
|
14
25
|
],
|
|
15
26
|
branches: ['main'],
|
|
16
27
|
};
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# [2.21.0](https://github.com/adobe/helix-rum-enhancer/compare/v2.20.0...v2.21.0) (2024-09-18)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **dom:** apply change ([67803e6](https://github.com/adobe/helix-rum-enhancer/commit/67803e60f57ef29b9ea1c51eea26d99c778b5f97))
|
|
7
|
+
* **dom:** better handling of empty type form inputs ([d53deb5](https://github.com/adobe/helix-rum-enhancer/commit/d53deb5c6e94ff18df2a01996c39b5cff7636fe4))
|
|
8
|
+
* **dom:** better handling of fake blocks ([66a9049](https://github.com/adobe/helix-rum-enhancer/commit/66a90497a45d754ed0fa4e8f8bc0917be64b3245))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **checkpoints:** add support for the `language` checkpoint ([f99755a](https://github.com/adobe/helix-rum-enhancer/commit/f99755a5cc8eef83b655a6beff5e7e371196c90d))
|
|
14
|
+
* **dom:** more fine-grained sourceselector implementation ([ada279d](https://github.com/adobe/helix-rum-enhancer/commit/ada279d423590fb3feeb304b3a57d6da4ee3c9bf))
|
|
15
|
+
|
|
1
16
|
# [2.20.0](https://github.com/adobe/helix-rum-enhancer/compare/v2.19.2...v2.20.0) (2024-08-19)
|
|
2
17
|
|
|
3
18
|
|
package/README.md
CHANGED
|
@@ -23,6 +23,49 @@ It will add following new checkpoints:
|
|
|
23
23
|
- `reload`, `navigate`, `enter`: depending on how the current page was accessed
|
|
24
24
|
- `formsubmit`: when a form is submitted
|
|
25
25
|
|
|
26
|
+
### The `source` parameter and the `sourceSelector`
|
|
27
|
+
|
|
28
|
+
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.
|
|
29
|
+
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
|
|
30
|
+
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
|
|
31
|
+
be better represented by semantic HTML elements.
|
|
32
|
+
|
|
33
|
+
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.
|
|
34
|
+
|
|
35
|
+
The structure of the `source` parameter is as follows:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
<context> <element>#<identifier>
|
|
39
|
+
```
|
|
40
|
+
All three parts are optional
|
|
41
|
+
|
|
42
|
+
`context` is
|
|
43
|
+
- `form` for form elements
|
|
44
|
+
- `dialog` for dialog elements, or parent containers that are fixed positioned and have a positive high z-index
|
|
45
|
+
- `.block-name` for Helix blocks
|
|
46
|
+
- `header`, `footer`, `nav`, `aside` for main site structure
|
|
47
|
+
- `#id` as a fallback, if a container ID is available
|
|
48
|
+
|
|
49
|
+
`element` is
|
|
50
|
+
- `button` for buttons, or links that look like buttons (e.g. with a class `button` or `btn` or `cta`)
|
|
51
|
+
- `img` for images
|
|
52
|
+
- `video` for videos
|
|
53
|
+
- `a` for links that are not buttons
|
|
54
|
+
- `input[type="text"]` for input elements (all types are supported)
|
|
55
|
+
- `select`, `textarea`, etc. for other form elements
|
|
56
|
+
|
|
57
|
+
`identifier` is
|
|
58
|
+
- the `id` attribute of the element, if provided
|
|
59
|
+
- the first `.class` if there are any
|
|
60
|
+
- else omitted
|
|
61
|
+
|
|
62
|
+
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.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
#### Examples
|
|
66
|
+
|
|
67
|
+
- ``
|
|
68
|
+
|
|
26
69
|
## Development
|
|
27
70
|
|
|
28
71
|
### Build
|
package/modules/dom.js
CHANGED
|
@@ -30,6 +30,59 @@ export const targetSelector = (element) => {
|
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
function walk(element, checkFn) {
|
|
34
|
+
if (!element || element === document.body || element === document.documentElement) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const checkValue = checkFn(element);
|
|
38
|
+
return checkValue || walk(element.parentElement, checkFn);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isDialog(element) {
|
|
42
|
+
// doing it well
|
|
43
|
+
if (element.tagName === 'DIALOG') return true;
|
|
44
|
+
// making the best of it
|
|
45
|
+
if (element.getAttribute('role') === 'dialog') return true;
|
|
46
|
+
if (element.getAttribute('role') === 'alertdialog') return true;
|
|
47
|
+
if (element.getAttribute('aria-modal') === 'true') return true;
|
|
48
|
+
// doing it wrong
|
|
49
|
+
const computedStyle = window.getComputedStyle(element);
|
|
50
|
+
return (computedStyle && computedStyle.position === 'fixed' && computedStyle.zIndex > 100);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isButton(element) {
|
|
54
|
+
if (element.tagName === 'BUTTON') return true;
|
|
55
|
+
if (element.tagName === 'INPUT' && element.getAttribute('type') === 'button') return true;
|
|
56
|
+
if (element.tagName === 'A') {
|
|
57
|
+
const classes = Array.from(element.classList);
|
|
58
|
+
return classes.some((className) => className.match(/button|cta/));
|
|
59
|
+
}
|
|
60
|
+
return element.getAttribute('role') === 'button';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getSourceContext(element) {
|
|
64
|
+
if (element.closest('form')) return 'form';
|
|
65
|
+
const block = element.closest('.block[data-block-name]');
|
|
66
|
+
if (block) return `.${block.getAttribute('data-block-name')}`;
|
|
67
|
+
if (walk(element, isDialog)) return 'dialog';
|
|
68
|
+
if (element.closest('nav')) return 'nav';
|
|
69
|
+
if (element.closest('header')) return 'header';
|
|
70
|
+
if (element.closest('footer')) return 'footer';
|
|
71
|
+
if (element.closest('aside')) return 'aside';
|
|
72
|
+
return (walk(element, (e) => e.id && `#${e.id}`));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getSourceElement(element) {
|
|
76
|
+
if (element.closest('form') && Array.from(element.closest('form').elements).includes(element)) return element.tagName.toLowerCase() + (element.tagName === 'INPUT' ? `[type='${element.getAttribute('type') || ''}']` : '');
|
|
77
|
+
if (walk(element, isButton)) return 'button';
|
|
78
|
+
return element.tagName.toLowerCase().match(/^(a|img|video)$/) && element.tagName.toLowerCase();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getSourceIdentifier(element) {
|
|
82
|
+
if (element.id) return `#${element.id}`;
|
|
83
|
+
if (element.getAttribute('data-block-name')) return `.${element.getAttribute('data-block-name')}`;
|
|
84
|
+
return (element.classList.length > 0 && `.${element.classList[0]}`);
|
|
85
|
+
}
|
|
33
86
|
export const sourceSelector = (element) => {
|
|
34
87
|
try {
|
|
35
88
|
if (!element || element === document.body || element === document.documentElement) {
|
|
@@ -38,41 +91,10 @@ export const sourceSelector = (element) => {
|
|
|
38
91
|
if (element.getAttribute('data-rum-source')) {
|
|
39
92
|
return element.getAttribute('data-rum-source');
|
|
40
93
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const blockName = element.closest('.block') ? element.closest('.block').getAttribute('data-block-name') : '';
|
|
48
|
-
if (element.id || formElementSelector) {
|
|
49
|
-
const id = element.id ? `#${element.id}` : '';
|
|
50
|
-
return blockName ? `.${blockName} ${formElementSelector}${id}` : `${formElementSelector}${id}`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (element.getAttribute('data-block-name')) {
|
|
54
|
-
return `.${element.getAttribute('data-block-name')}`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const classes = Array.from(element.classList);
|
|
58
|
-
const label = element.tagName.toLowerCase();
|
|
59
|
-
const firstClass = classes.length > 0 ? `.${classes[0]}` : '';
|
|
60
|
-
const labelWithClass = `${element.tagName.toLowerCase()}${firstClass}`;
|
|
61
|
-
if (element.tagName.toLowerCase() === 'button'
|
|
62
|
-
|| element.type === 'button'
|
|
63
|
-
|| classes.some((className) => className.match(/button|cta/))) {
|
|
64
|
-
let parent = element.parentElement;
|
|
65
|
-
if (!parent) return labelWithClass;
|
|
66
|
-
if (parent.id) return `#${parent.id} ${label}`;
|
|
67
|
-
while (parent.tagName !== 'BODY' && !parent.id) parent = parent.parentElement;
|
|
68
|
-
if (parent.id) return `#${parent.id} ${labelWithClass}`;
|
|
69
|
-
return blockName ? `.${blockName} ${labelWithClass}` : labelWithClass;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const parent = sourceSelector(element.parentElement);
|
|
73
|
-
if (parent) return parent;
|
|
74
|
-
|
|
75
|
-
return labelWithClass;
|
|
94
|
+
const context = getSourceContext(element.parentElement) || '';
|
|
95
|
+
const elementName = getSourceElement(element) || '';
|
|
96
|
+
const identifier = getSourceIdentifier(element) || '';
|
|
97
|
+
return `${context} ${elementName}${identifier}`.trim() || `"${element.textContent.substring(0, 10)}"`;
|
|
76
98
|
/* c8 ignore next 3 */
|
|
77
99
|
} catch (error) {
|
|
78
100
|
return null;
|
package/modules/fflags.js
CHANGED
package/modules/index.js
CHANGED
|
@@ -267,6 +267,11 @@ function addTrackingFromConfig() {
|
|
|
267
267
|
addCookieConsentTracking(sampleRUM);
|
|
268
268
|
addAdsParametersTracking(sampleRUM);
|
|
269
269
|
addEmailParameterTracking(sampleRUM);
|
|
270
|
+
fflags.enabled('language', () => {
|
|
271
|
+
const target = navigator.language;
|
|
272
|
+
const source = document.documentElement.lang;
|
|
273
|
+
sampleRUM('language', { source, target });
|
|
274
|
+
});
|
|
270
275
|
}
|
|
271
276
|
|
|
272
277
|
function initEnhancer() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/helix-rum-enhancer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.21.0",
|
|
4
4
|
"description": "Helix RUM Enhancer",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"reporter-options": "configFile=.mocha-multi.json"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@adobe/eslint-config-helix": "2.0.
|
|
37
|
-
"@adobe/helix-rum-js": "2.
|
|
36
|
+
"@adobe/eslint-config-helix": "2.0.7",
|
|
37
|
+
"@adobe/helix-rum-js": "2.4.0",
|
|
38
38
|
"@esm-bundle/chai": "4.3.4-fix.0",
|
|
39
39
|
"@semantic-release/changelog": "6.0.3",
|
|
40
40
|
"@semantic-release/git": "10.0.1",
|
|
@@ -47,15 +47,16 @@
|
|
|
47
47
|
"codecov": "3.8.3",
|
|
48
48
|
"eslint": "8.57.0",
|
|
49
49
|
"eslint-plugin-header": "3.1.1",
|
|
50
|
-
"eslint-plugin-import": "2.
|
|
51
|
-
"husky": "9.1.
|
|
52
|
-
"jsdoc-to-markdown": "
|
|
50
|
+
"eslint-plugin-import": "2.30.0",
|
|
51
|
+
"husky": "9.1.5",
|
|
52
|
+
"jsdoc-to-markdown": "9.0.0",
|
|
53
53
|
"junit-report-builder": "5.0.0",
|
|
54
|
-
"lint-staged": "15.2.
|
|
54
|
+
"lint-staged": "15.2.10",
|
|
55
55
|
"mocha": "10.7.3",
|
|
56
56
|
"mocha-multi-reporters": "1.5.1",
|
|
57
|
-
"rollup": "4.
|
|
57
|
+
"rollup": "4.21.2",
|
|
58
58
|
"rollup-plugin-cleanup": "3.2.1",
|
|
59
|
+
"rollup-plugin-checksum": "1.0.1",
|
|
59
60
|
"rollup-plugin-eslint-bundle": "9.0.0",
|
|
60
61
|
"semantic-release": "24.1.0",
|
|
61
62
|
"web-vitals": "4.2.3"
|
package/rollup.config.js
CHANGED
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import cleanup from 'rollup-plugin-cleanup';
|
|
14
|
-
import
|
|
14
|
+
import pkg from 'rollup-plugin-checksum';
|
|
15
|
+
|
|
16
|
+
const checksum = pkg.default;
|
|
15
17
|
|
|
16
18
|
const banner = `/*
|
|
17
19
|
* Copyright 2024 Adobe. All rights reserved.
|
|
@@ -23,10 +25,7 @@ const banner = `/*
|
|
|
23
25
|
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
24
26
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
25
27
|
* governing permissions and limitations under the License.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
/* eslint-disable max-classes-per-file, wrap-iife */
|
|
29
|
-
// eslint-disable-next-line func-names`;
|
|
28
|
+
*/`;
|
|
30
29
|
|
|
31
30
|
const bundles = [
|
|
32
31
|
{
|
|
@@ -55,13 +54,12 @@ export default [...bundles.map(({ outputFile, source }) => ({
|
|
|
55
54
|
],
|
|
56
55
|
plugins: [
|
|
57
56
|
cleanup({
|
|
58
|
-
comments: [
|
|
59
|
-
maxEmptyLines:
|
|
57
|
+
comments: [],
|
|
58
|
+
maxEmptyLines: 0,
|
|
60
59
|
}),
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
},
|
|
60
|
+
checksum({
|
|
61
|
+
filename: `${outputFile.split('/').pop()}.md5`,
|
|
62
|
+
includeAssets: false,
|
|
65
63
|
}),
|
|
66
64
|
],
|
|
67
65
|
}))];
|
package/src/index.js
CHANGED
|
@@ -9,9 +9,6 @@
|
|
|
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
|
-
/* eslint-disable max-classes-per-file, wrap-iife */
|
|
14
|
-
// eslint-disable-next-line func-names
|
|
15
12
|
(function () {
|
|
16
13
|
'use strict';
|
|
17
14
|
|
|
@@ -21,10 +18,10 @@
|
|
|
21
18
|
.reduce((a, b) => a + b, 1) % 1371) !== -1
|
|
22
19
|
|| !!window.origin.match(/localhost/),
|
|
23
20
|
enabled: (flag, callback) => fflags.has(flag) && callback(),
|
|
24
|
-
|
|
25
21
|
disabled: (flag, callback) => !fflags.has(flag) && callback(),
|
|
26
22
|
eagercwv: [683],
|
|
27
23
|
example: [543, 770, 1136],
|
|
24
|
+
language: [543, 959, 1139, 620],
|
|
28
25
|
};
|
|
29
26
|
|
|
30
27
|
const KNOWN_PROPERTIES = ['weight', 'id', 'referer', 'checkpoint', 't', 'source', 'target', 'cwv', 'CLS', 'FID', 'LCP', 'INP', 'TTFB'];
|
|
@@ -32,26 +29,8 @@
|
|
|
32
29
|
fflags.enabled('example', () => DEFAULT_TRACKING_EVENTS.push('example'));
|
|
33
30
|
|
|
34
31
|
const urlSanitizers = {
|
|
35
|
-
/**
|
|
36
|
-
* Returns the full url.
|
|
37
|
-
* If no url is provided, it defaults to window.location.href.
|
|
38
|
-
* @param {string} url (default: window.location.href) The url to sanitize
|
|
39
|
-
* @returns {string} The sanitized url
|
|
40
|
-
*/
|
|
41
32
|
full: (url = window.location.href) => new URL(url).toString(),
|
|
42
|
-
/**
|
|
43
|
-
* Returns the origin of the provided url.
|
|
44
|
-
* If no url is provided, it defaults to window.location.href.
|
|
45
|
-
* @param {string} url (default: window.location.href) The url to sanitize
|
|
46
|
-
* @returns {string} The sanitized url
|
|
47
|
-
*/
|
|
48
33
|
origin: (url = window.location.href) => new URL(url).origin,
|
|
49
|
-
/**
|
|
50
|
-
* Returns the sanitized url: the origin and the path (no query params or hash)
|
|
51
|
-
* If no url is provided, it defaults to window.location.href.
|
|
52
|
-
* @param {string} url (default: window.location.href) The url to sanitize
|
|
53
|
-
* @returns {string} The sanitized url
|
|
54
|
-
*/
|
|
55
34
|
path: (url = window.location.href) => {
|
|
56
35
|
const u = new URL(url);
|
|
57
36
|
return `${u.origin}${u.pathname}`;
|
|
@@ -60,7 +39,6 @@
|
|
|
60
39
|
|
|
61
40
|
const getTargetValue = (element) => element.getAttribute('data-rum-target') || element.getAttribute('href')
|
|
62
41
|
|| element.currentSrc || element.getAttribute('src') || element.dataset.action || element.action;
|
|
63
|
-
|
|
64
42
|
const targetSelector = (element) => {
|
|
65
43
|
try {
|
|
66
44
|
if (!element) return undefined;
|
|
@@ -69,7 +47,6 @@
|
|
|
69
47
|
value = getTargetValue(element.closest('a'));
|
|
70
48
|
}
|
|
71
49
|
if (value && !value.startsWith('https://')) {
|
|
72
|
-
// resolve relative links
|
|
73
50
|
value = new URL(value, window.location).href;
|
|
74
51
|
}
|
|
75
52
|
return value;
|
|
@@ -77,7 +54,51 @@
|
|
|
77
54
|
return null;
|
|
78
55
|
}
|
|
79
56
|
};
|
|
80
|
-
|
|
57
|
+
function walk(element, checkFn) {
|
|
58
|
+
if (!element || element === document.body || element === document.documentElement) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const checkValue = checkFn(element);
|
|
62
|
+
return checkValue || walk(element.parentElement, checkFn);
|
|
63
|
+
}
|
|
64
|
+
function isDialog(element) {
|
|
65
|
+
if (element.tagName === 'DIALOG') return true;
|
|
66
|
+
if (element.getAttribute('role') === 'dialog') return true;
|
|
67
|
+
if (element.getAttribute('role') === 'alertdialog') return true;
|
|
68
|
+
if (element.getAttribute('aria-modal') === 'true') return true;
|
|
69
|
+
const computedStyle = window.getComputedStyle(element);
|
|
70
|
+
return (computedStyle && computedStyle.position === 'fixed' && computedStyle.zIndex > 100);
|
|
71
|
+
}
|
|
72
|
+
function isButton(element) {
|
|
73
|
+
if (element.tagName === 'BUTTON') return true;
|
|
74
|
+
if (element.tagName === 'INPUT' && element.getAttribute('type') === 'button') return true;
|
|
75
|
+
if (element.tagName === 'A') {
|
|
76
|
+
const classes = Array.from(element.classList);
|
|
77
|
+
return classes.some((className) => className.match(/button|cta/));
|
|
78
|
+
}
|
|
79
|
+
return element.getAttribute('role') === 'button';
|
|
80
|
+
}
|
|
81
|
+
function getSourceContext(element) {
|
|
82
|
+
if (element.closest('form')) return 'form';
|
|
83
|
+
const block = element.closest('.block[data-block-name]');
|
|
84
|
+
if (block) return `.${block.getAttribute('data-block-name')}`;
|
|
85
|
+
if (walk(element, isDialog)) return 'dialog';
|
|
86
|
+
if (element.closest('nav')) return 'nav';
|
|
87
|
+
if (element.closest('header')) return 'header';
|
|
88
|
+
if (element.closest('footer')) return 'footer';
|
|
89
|
+
if (element.closest('aside')) return 'aside';
|
|
90
|
+
return (walk(element, (e) => e.id && `#${e.id}`));
|
|
91
|
+
}
|
|
92
|
+
function getSourceElement(element) {
|
|
93
|
+
if (element.closest('form') && Array.from(element.closest('form').elements).includes(element)) return element.tagName.toLowerCase() + (element.tagName === 'INPUT' ? `[type='${element.getAttribute('type') || ''}']` : '');
|
|
94
|
+
if (walk(element, isButton)) return 'button';
|
|
95
|
+
return element.tagName.toLowerCase().match(/^(a|img|video)$/) && element.tagName.toLowerCase();
|
|
96
|
+
}
|
|
97
|
+
function getSourceIdentifier(element) {
|
|
98
|
+
if (element.id) return `#${element.id}`;
|
|
99
|
+
if (element.getAttribute('data-block-name')) return `.${element.getAttribute('data-block-name')}`;
|
|
100
|
+
return (element.classList.length > 0 && `.${element.classList[0]}`);
|
|
101
|
+
}
|
|
81
102
|
const sourceSelector = (element) => {
|
|
82
103
|
try {
|
|
83
104
|
if (!element || element === document.body || element === document.documentElement) {
|
|
@@ -86,41 +107,10 @@
|
|
|
86
107
|
if (element.getAttribute('data-rum-source')) {
|
|
87
108
|
return element.getAttribute('data-rum-source');
|
|
88
109
|
}
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const blockName = element.closest('.block') ? element.closest('.block').getAttribute('data-block-name') : '';
|
|
96
|
-
if (element.id || formElementSelector) {
|
|
97
|
-
const id = element.id ? `#${element.id}` : '';
|
|
98
|
-
return blockName ? `.${blockName} ${formElementSelector}${id}` : `${formElementSelector}${id}`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (element.getAttribute('data-block-name')) {
|
|
102
|
-
return `.${element.getAttribute('data-block-name')}`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const classes = Array.from(element.classList);
|
|
106
|
-
const label = element.tagName.toLowerCase();
|
|
107
|
-
const firstClass = classes.length > 0 ? `.${classes[0]}` : '';
|
|
108
|
-
const labelWithClass = `${element.tagName.toLowerCase()}${firstClass}`;
|
|
109
|
-
if (element.tagName.toLowerCase() === 'button'
|
|
110
|
-
|| element.type === 'button'
|
|
111
|
-
|| classes.some((className) => className.match(/button|cta/))) {
|
|
112
|
-
let parent = element.parentElement;
|
|
113
|
-
if (!parent) return labelWithClass;
|
|
114
|
-
if (parent.id) return `#${parent.id} ${label}`;
|
|
115
|
-
while (parent.tagName !== 'BODY' && !parent.id) parent = parent.parentElement;
|
|
116
|
-
if (parent.id) return `#${parent.id} ${labelWithClass}`;
|
|
117
|
-
return blockName ? `.${blockName} ${labelWithClass}` : labelWithClass;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const parent = sourceSelector(element.parentElement);
|
|
121
|
-
if (parent) return parent;
|
|
122
|
-
|
|
123
|
-
return labelWithClass;
|
|
110
|
+
const context = getSourceContext(element.parentElement) || '';
|
|
111
|
+
const elementName = getSourceElement(element) || '';
|
|
112
|
+
const identifier = getSourceIdentifier(element) || '';
|
|
113
|
+
return `${context} ${elementName}${identifier}`.trim() || `"${element.textContent.substring(0, 10)}"`;
|
|
124
114
|
} catch (error) {
|
|
125
115
|
return null;
|
|
126
116
|
}
|
|
@@ -130,12 +120,10 @@
|
|
|
130
120
|
const cmpCookie = document.cookie.split(';')
|
|
131
121
|
.map((c) => c.trim())
|
|
132
122
|
.find((cookie) => cookie.startsWith('OptanonAlertBoxClosed='));
|
|
133
|
-
|
|
134
123
|
if (cmpCookie) {
|
|
135
124
|
sampleRUM('consent', { source: 'onetrust', target: 'hidden' });
|
|
136
125
|
return;
|
|
137
126
|
}
|
|
138
|
-
|
|
139
127
|
let consentMutationObserver;
|
|
140
128
|
const trackShowConsent = () => {
|
|
141
129
|
if (document.querySelector('body > div#onetrust-consent-sdk')) {
|
|
@@ -147,27 +135,22 @@
|
|
|
147
135
|
}
|
|
148
136
|
return false;
|
|
149
137
|
};
|
|
150
|
-
|
|
151
138
|
if (!trackShowConsent()) {
|
|
152
|
-
// eslint-disable-next-line max-len
|
|
153
139
|
consentMutationObserver = window.MutationObserver
|
|
154
140
|
? new MutationObserver(trackShowConsent)
|
|
155
|
-
:
|
|
141
|
+
: null;
|
|
156
142
|
if (consentMutationObserver) {
|
|
157
143
|
consentMutationObserver.observe(
|
|
158
144
|
document.body,
|
|
159
|
-
// eslint-disable-next-line object-curly-newline
|
|
160
145
|
{ attributes: false, childList: true, subtree: false },
|
|
161
146
|
);
|
|
162
147
|
}
|
|
163
148
|
}
|
|
164
149
|
}
|
|
165
|
-
|
|
166
150
|
function addUTMParametersTracking(sampleRUM) {
|
|
167
151
|
const usp = new URLSearchParams(window.location.search);
|
|
168
152
|
[...usp.entries()]
|
|
169
153
|
.filter(([key]) => key.startsWith('utm_'))
|
|
170
|
-
// exclude keys that may leak PII
|
|
171
154
|
.filter(([key]) => key !== 'utm_id')
|
|
172
155
|
.filter(([key]) => key !== 'utm_term')
|
|
173
156
|
.forEach(([source, target]) => sampleRUM('utm', { source, target }));
|
|
@@ -200,23 +183,16 @@
|
|
|
200
183
|
}
|
|
201
184
|
|
|
202
185
|
const { sampleRUM, queue, isSelected } = (window.hlx && window.hlx.rum) ? window.hlx.rum
|
|
203
|
-
|
|
204
|
-
|
|
186
|
+
: {};
|
|
205
187
|
const formSubmitListener = (e) => sampleRUM('formsubmit', { target: targetSelector(e.target), source: sourceSelector(e.target) });
|
|
206
|
-
|
|
207
|
-
// eslint-disable-next-line no-use-before-define, max-len
|
|
208
188
|
const blocksMutationObserver = window.MutationObserver ? new MutationObserver(blocksMutationsCallback)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// eslint-disable-next-line no-use-before-define, max-len
|
|
189
|
+
: {};
|
|
212
190
|
const mediaMutationObserver = window.MutationObserver ? new MutationObserver(mediaMutationsCallback)
|
|
213
|
-
|
|
214
|
-
|
|
191
|
+
: {};
|
|
215
192
|
function trackCheckpoint(checkpoint, data, t) {
|
|
216
193
|
const { weight, id } = window.hlx.rum;
|
|
217
194
|
if (isSelected) {
|
|
218
195
|
const sendPing = (pdata = data) => {
|
|
219
|
-
// eslint-disable-next-line object-curly-newline, max-len
|
|
220
196
|
const body = JSON.stringify({ weight, id, referer: urlSanitizers[window.hlx.RUM_MASK_URL || 'path'](), checkpoint, t, ...data }, KNOWN_PROPERTIES);
|
|
221
197
|
const { href: url, origin } = new URL(`.rum/${weight}`, sampleRUM.collectBaseURL || sampleRUM.baseURL);
|
|
222
198
|
if (window.location.origin === origin) {
|
|
@@ -225,26 +201,22 @@
|
|
|
225
201
|
} else {
|
|
226
202
|
navigator.sendBeacon(url, body);
|
|
227
203
|
}
|
|
228
|
-
// eslint-disable-next-line no-console
|
|
229
204
|
console.debug(`ping:${checkpoint}`, pdata);
|
|
230
205
|
};
|
|
231
206
|
sendPing(data);
|
|
232
207
|
}
|
|
233
208
|
}
|
|
234
|
-
|
|
235
209
|
function processQueue() {
|
|
236
210
|
while (queue && queue.length) {
|
|
237
211
|
const ck = queue.shift();
|
|
238
212
|
trackCheckpoint(...ck);
|
|
239
213
|
}
|
|
240
214
|
}
|
|
241
|
-
|
|
242
215
|
function addCWVTracking() {
|
|
243
216
|
setTimeout(() => {
|
|
244
217
|
try {
|
|
245
218
|
const cwvScript = new URL('.rum/web-vitals/dist/web-vitals.iife.js', sampleRUM.baseURL).href;
|
|
246
219
|
if (document.querySelector(`script[src="${cwvScript}"]`)) {
|
|
247
|
-
// web vitals script has been loaded already
|
|
248
220
|
return;
|
|
249
221
|
}
|
|
250
222
|
const script = document.createElement('script');
|
|
@@ -260,11 +232,7 @@
|
|
|
260
232
|
}
|
|
261
233
|
sampleRUM('cwv', data);
|
|
262
234
|
};
|
|
263
|
-
|
|
264
235
|
const isEager = (metric) => ['CLS', 'LCP'].includes(metric);
|
|
265
|
-
|
|
266
|
-
// When loading `web-vitals` using a classic script, all the public
|
|
267
|
-
// methods can be found on the `webVitals` global namespace.
|
|
268
236
|
['FID', 'INP', 'TTFB', 'CLS', 'LCP'].forEach((metric) => {
|
|
269
237
|
const metricFn = window.webVitals[`on${metric}`];
|
|
270
238
|
if (typeof metricFn === 'function') {
|
|
@@ -278,32 +246,26 @@
|
|
|
278
246
|
};
|
|
279
247
|
document.head.appendChild(script);
|
|
280
248
|
} catch (error) {
|
|
281
|
-
// something went wrong
|
|
282
249
|
}
|
|
283
|
-
}, 2000);
|
|
250
|
+
}, 2000);
|
|
284
251
|
}
|
|
285
|
-
|
|
286
252
|
function addNavigationTracking() {
|
|
287
|
-
// enter checkpoint when referrer is not the current page url
|
|
288
253
|
const navigate = (source, type) => {
|
|
289
254
|
const payload = { source, target: document.visibilityState };
|
|
290
|
-
// reload: same page, navigate: same origin, enter: everything else
|
|
291
255
|
if (type === 'reload' || source === window.location.href) {
|
|
292
256
|
sampleRUM('reload', payload);
|
|
293
257
|
} else if (type && type !== 'navigate') {
|
|
294
|
-
sampleRUM(type, payload);
|
|
258
|
+
sampleRUM(type, payload);
|
|
295
259
|
} else if (source && window.location.origin === new URL(source).origin) {
|
|
296
|
-
sampleRUM('navigate', payload);
|
|
260
|
+
sampleRUM('navigate', payload);
|
|
297
261
|
} else {
|
|
298
|
-
sampleRUM('enter', payload);
|
|
262
|
+
sampleRUM('enter', payload);
|
|
299
263
|
}
|
|
300
264
|
};
|
|
301
|
-
|
|
302
265
|
new PerformanceObserver((list) => list
|
|
303
266
|
.getEntries().map((entry) => navigate(window.hlx.referrer || document.referrer, entry.type)))
|
|
304
267
|
.observe({ type: 'navigation', buffered: true });
|
|
305
268
|
}
|
|
306
|
-
|
|
307
269
|
function addLoadResourceTracking() {
|
|
308
270
|
const observer = new PerformanceObserver((list) => {
|
|
309
271
|
try {
|
|
@@ -320,12 +282,10 @@
|
|
|
320
282
|
sampleRUM('missingresource', { source: entry.name, target: entry.hostname });
|
|
321
283
|
});
|
|
322
284
|
} catch (error) {
|
|
323
|
-
// something went wrong
|
|
324
285
|
}
|
|
325
286
|
});
|
|
326
287
|
observer.observe({ type: 'resource', buffered: true });
|
|
327
288
|
}
|
|
328
|
-
|
|
329
289
|
function activateBlocksMutationObserver() {
|
|
330
290
|
if (!blocksMutationObserver || blocksMutationObserver.active) {
|
|
331
291
|
return;
|
|
@@ -333,11 +293,9 @@
|
|
|
333
293
|
blocksMutationObserver.active = true;
|
|
334
294
|
blocksMutationObserver.observe(
|
|
335
295
|
document.body,
|
|
336
|
-
// eslint-disable-next-line object-curly-newline
|
|
337
296
|
{ subtree: true, attributes: true, attributeFilter: ['data-block-status'] },
|
|
338
297
|
);
|
|
339
298
|
}
|
|
340
|
-
|
|
341
299
|
function activateMediaMutationObserver() {
|
|
342
300
|
if (!mediaMutationObserver || mediaMutationObserver.active) {
|
|
343
301
|
return;
|
|
@@ -345,11 +303,9 @@
|
|
|
345
303
|
mediaMutationObserver.active = true;
|
|
346
304
|
mediaMutationObserver.observe(
|
|
347
305
|
document.body,
|
|
348
|
-
// eslint-disable-next-line object-curly-newline
|
|
349
306
|
{ subtree: true, attributes: false, childList: true },
|
|
350
307
|
);
|
|
351
308
|
}
|
|
352
|
-
|
|
353
309
|
function getIntersectionObsever(checkpoint) {
|
|
354
310
|
if (!window.IntersectionObserver) {
|
|
355
311
|
return null;
|
|
@@ -361,13 +317,12 @@
|
|
|
361
317
|
entries
|
|
362
318
|
.filter((entry) => entry.isIntersecting)
|
|
363
319
|
.forEach((entry) => {
|
|
364
|
-
observer.unobserve(entry.target);
|
|
320
|
+
observer.unobserve(entry.target);
|
|
365
321
|
const target = targetSelector(entry.target);
|
|
366
322
|
const source = sourceSelector(entry.target);
|
|
367
323
|
sampleRUM(checkpoint, { target, source });
|
|
368
324
|
});
|
|
369
325
|
} catch (error) {
|
|
370
|
-
// something went wrong
|
|
371
326
|
}
|
|
372
327
|
});
|
|
373
328
|
return observer;
|
|
@@ -379,7 +334,6 @@
|
|
|
379
334
|
blocks.forEach((b) => blockobserver.observe(b));
|
|
380
335
|
}
|
|
381
336
|
}
|
|
382
|
-
|
|
383
337
|
const observedMedia = new Set();
|
|
384
338
|
function addViewMediaTracking(parent) {
|
|
385
339
|
const mediaobserver = getIntersectionObsever('viewmedia');
|
|
@@ -392,22 +346,18 @@
|
|
|
392
346
|
});
|
|
393
347
|
}
|
|
394
348
|
}
|
|
395
|
-
|
|
396
349
|
function addFormTracking(parent) {
|
|
397
350
|
activateBlocksMutationObserver();
|
|
398
351
|
activateMediaMutationObserver();
|
|
399
352
|
parent.querySelectorAll('form').forEach((form) => {
|
|
400
|
-
form.removeEventListener('submit', formSubmitListener);
|
|
353
|
+
form.removeEventListener('submit', formSubmitListener);
|
|
401
354
|
form.addEventListener('submit', formSubmitListener);
|
|
402
355
|
});
|
|
403
356
|
}
|
|
404
|
-
|
|
405
357
|
function addObserver(ck, fn, block) {
|
|
406
358
|
return DEFAULT_TRACKING_EVENTS.includes(ck) && fn(block);
|
|
407
359
|
}
|
|
408
|
-
|
|
409
360
|
function blocksMutationsCallback(mutations) {
|
|
410
|
-
// block specific mutations
|
|
411
361
|
mutations
|
|
412
362
|
.filter((m) => m.type === 'attributes' && m.attributeName === 'data-block-status')
|
|
413
363
|
.filter((m) => m.target.dataset.blockStatus === 'loaded')
|
|
@@ -416,15 +366,12 @@
|
|
|
416
366
|
addObserver('viewblock', addViewBlockTracking, m.target);
|
|
417
367
|
});
|
|
418
368
|
}
|
|
419
|
-
|
|
420
369
|
function mediaMutationsCallback(mutations) {
|
|
421
|
-
// media mutations
|
|
422
370
|
mutations
|
|
423
371
|
.forEach((m) => {
|
|
424
372
|
addObserver('viewmedia', addViewMediaTracking, m.target);
|
|
425
373
|
});
|
|
426
374
|
}
|
|
427
|
-
|
|
428
375
|
function addTrackingFromConfig() {
|
|
429
376
|
document.addEventListener('click', (event) => {
|
|
430
377
|
sampleRUM('click', { target: targetSelector(event.target), source: sourceSelector(event.target) });
|
|
@@ -439,8 +386,12 @@
|
|
|
439
386
|
addCookieConsentTracking(sampleRUM);
|
|
440
387
|
addAdsParametersTracking(sampleRUM);
|
|
441
388
|
addEmailParameterTracking(sampleRUM);
|
|
389
|
+
fflags.enabled('language', () => {
|
|
390
|
+
const target = navigator.language;
|
|
391
|
+
const source = document.documentElement.lang;
|
|
392
|
+
sampleRUM('language', { source, target });
|
|
393
|
+
});
|
|
442
394
|
}
|
|
443
|
-
|
|
444
395
|
function initEnhancer() {
|
|
445
396
|
try {
|
|
446
397
|
if (sampleRUM) {
|
|
@@ -449,9 +400,8 @@
|
|
|
449
400
|
processQueue();
|
|
450
401
|
}
|
|
451
402
|
} catch (error) {
|
|
452
|
-
// something went wrong
|
|
453
403
|
}
|
|
454
404
|
}
|
|
455
|
-
|
|
456
405
|
initEnhancer();
|
|
406
|
+
|
|
457
407
|
})();
|
package/src/index.md5
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
65cf540e7fe93b6d3d67567cbf215544
|
|
@@ -43,7 +43,7 @@ export default {
|
|
|
43
43
|
context.url = '/node_modules/@adobe/helix-rum-js/dist/rum-standalone.js';
|
|
44
44
|
await next();
|
|
45
45
|
context.body = context.body
|
|
46
|
-
.replace(/const weight.*/, 'const weight = 1
|
|
46
|
+
.replace(/const weight.*/, 'const weight = 1')
|
|
47
47
|
.replace(/navigator\.sendBeacon/g, 'fakeSendBeacon')
|
|
48
48
|
.replace('.rum/@adobe/helix-rum-enhancer@^2/src/index.js', 'src/index.map.js');
|
|
49
49
|
return true;
|