@adobe/helix-rum-enhancer 2.26.0-beta.1 → 2.26.1
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 +1 -1
- package/CHANGELOG.md +9 -2
- package/modules/dom.js +57 -56
- package/modules/index.js +42 -37
- package/modules/martech.js +6 -6
- package/modules/utils.js +5 -3
- package/package.json +9 -9
- package/src/index.js +101 -101
- package/src/index.md5 +1 -1
- package/web-test-runner.config.js +4 -2
package/.releaserc.cjs
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
## [2.26.1](https://github.com/adobe/helix-rum-enhancer/compare/v2.26.0...v2.26.1) (2024-12-10)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **redirects:** use underscore in param ([bada92e](https://github.com/adobe/helix-rum-enhancer/commit/bada92e69aaa8f560d61d6264f578eb2958934ec))
|
|
7
|
+
|
|
8
|
+
# [2.26.0](https://github.com/adobe/helix-rum-enhancer/compare/v2.25.0...v2.26.0) (2024-10-16)
|
|
2
9
|
|
|
3
10
|
|
|
4
11
|
### Features
|
|
5
12
|
|
|
6
|
-
* handle pre-rendering ([
|
|
13
|
+
* handle pre-speculation rules and rendering ([#301](https://github.com/adobe/helix-rum-enhancer/issues/301)) ([196d404](https://github.com/adobe/helix-rum-enhancer/commit/196d404f3f90a247a223e8ddc7c667592a87319f))
|
|
7
14
|
|
|
8
15
|
# [2.25.0](https://github.com/adobe/helix-rum-enhancer/compare/v2.24.0...v2.25.0) (2024-10-03)
|
|
9
16
|
|
package/modules/dom.js
CHANGED
|
@@ -9,99 +9,100 @@
|
|
|
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
|
-
export const getTargetValue = (
|
|
13
|
-
||
|
|
12
|
+
export const getTargetValue = (el) => el.getAttribute('data-rum-target') || el.getAttribute('href')
|
|
13
|
+
|| el.currentSrc || el.getAttribute('src') || el.dataset.action || el.action;
|
|
14
14
|
|
|
15
|
-
export const targetSelector = (
|
|
15
|
+
export const targetSelector = (el) => {
|
|
16
16
|
try {
|
|
17
|
-
if (!
|
|
18
|
-
let
|
|
19
|
-
if (!
|
|
20
|
-
|
|
17
|
+
if (!el) return undefined;
|
|
18
|
+
let v = getTargetValue(el);
|
|
19
|
+
if (!v && el.tagName !== 'A' && el.closest('a')) {
|
|
20
|
+
v = getTargetValue(el.closest('a'));
|
|
21
21
|
}
|
|
22
|
-
if (
|
|
22
|
+
if (v && !v.startsWith('https://')) {
|
|
23
23
|
// resolve relative links
|
|
24
|
-
|
|
24
|
+
v = new URL(v, window.location).href;
|
|
25
25
|
}
|
|
26
|
-
return
|
|
26
|
+
return v;
|
|
27
27
|
/* c8 ignore next 3 */
|
|
28
28
|
} catch (error) {
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
function walk(
|
|
34
|
-
if (!
|
|
33
|
+
function walk(el, checkFn) {
|
|
34
|
+
if (!el || el === document.body || el === document.documentElement) {
|
|
35
35
|
return undefined;
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
return checkValue || walk(element.parentElement, checkFn);
|
|
37
|
+
return checkFn(el) || walk(el.parentElement, checkFn);
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
function isDialog(
|
|
40
|
+
function isDialog(el) {
|
|
42
41
|
// doing it well
|
|
43
|
-
if (
|
|
42
|
+
if (el.tagName === 'DIALOG') return true;
|
|
44
43
|
// making the best of it
|
|
45
|
-
if (
|
|
46
|
-
if (
|
|
47
|
-
if (
|
|
44
|
+
if (el.getAttribute('role') === 'dialog') return true;
|
|
45
|
+
if (el.getAttribute('role') === 'alertdialog') return true;
|
|
46
|
+
if (el.getAttribute('aria-modal') === 'true') return true;
|
|
48
47
|
// doing it wrong
|
|
49
|
-
const
|
|
50
|
-
return (
|
|
48
|
+
const cs = window.getComputedStyle(el);
|
|
49
|
+
return (cs && cs.position === 'fixed' && cs.zIndex > 100);
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
function isButton(
|
|
54
|
-
if (
|
|
55
|
-
if (
|
|
56
|
-
if (
|
|
57
|
-
const classes = Array.from(
|
|
52
|
+
function isButton(el) {
|
|
53
|
+
if (el.tagName === 'BUTTON') return true;
|
|
54
|
+
if (el.tagName === 'INPUT' && el.getAttribute('type') === 'button') return true;
|
|
55
|
+
if (el.tagName === 'A') {
|
|
56
|
+
const classes = Array.from(el.classList);
|
|
58
57
|
return classes.some((className) => className.match(/button|cta/));
|
|
59
58
|
}
|
|
60
|
-
return
|
|
59
|
+
return el.getAttribute('role') === 'button';
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
function getSourceContext(
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
function getSourceContext(el) {
|
|
63
|
+
const formEl = el.closest('form');
|
|
64
|
+
if (formEl) {
|
|
65
|
+
return `form${formEl.id ? `#${formEl.id}` : ''}`;
|
|
66
66
|
}
|
|
67
|
-
const block =
|
|
67
|
+
const block = el.closest('.block[data-block-name]');
|
|
68
68
|
if (block) return `.${block.getAttribute('data-block-name')}`;
|
|
69
|
-
if (walk(
|
|
70
|
-
if (
|
|
71
|
-
if (
|
|
72
|
-
if (
|
|
73
|
-
if (
|
|
74
|
-
return (walk(
|
|
69
|
+
if (walk(el, isDialog)) return 'dialog';
|
|
70
|
+
if (el.closest('nav')) return 'nav';
|
|
71
|
+
if (el.closest('header')) return 'header';
|
|
72
|
+
if (el.closest('footer')) return 'footer';
|
|
73
|
+
if (el.closest('aside')) return 'aside';
|
|
74
|
+
return (walk(el, (e) => e.id && `#${e.id}`));
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function getSourceElement(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
function getSourceElement(el) {
|
|
78
|
+
const f = el.closest('form');
|
|
79
|
+
if (f && Array.from(f.elements).includes(el)) {
|
|
80
|
+
return (el.tagName.toLowerCase()
|
|
81
|
+
+ (['INPUT', 'BUTTON'].includes(el.tagName)
|
|
82
|
+
? `[type='${el.getAttribute('type') || ''}']`
|
|
82
83
|
: ''));
|
|
83
84
|
}
|
|
84
|
-
if (walk(
|
|
85
|
-
return
|
|
85
|
+
if (walk(el, isButton)) return 'button';
|
|
86
|
+
return el.tagName.toLowerCase().match(/^(a|img|video)$/) && el.tagName.toLowerCase();
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
function getSourceIdentifier(
|
|
89
|
-
if (
|
|
90
|
-
if (
|
|
91
|
-
return (
|
|
89
|
+
function getSourceIdentifier(el) {
|
|
90
|
+
if (el.id) return `#${el.id}`;
|
|
91
|
+
if (el.getAttribute('data-block-name')) return `.${el.getAttribute('data-block-name')}`;
|
|
92
|
+
return (el.classList.length > 0 && `.${el.classList[0]}`);
|
|
92
93
|
}
|
|
93
|
-
export const sourceSelector = (
|
|
94
|
+
export const sourceSelector = (el) => {
|
|
94
95
|
try {
|
|
95
|
-
if (!
|
|
96
|
+
if (!el || el === document.body || el === document.documentElement) {
|
|
96
97
|
return undefined;
|
|
97
98
|
}
|
|
98
|
-
if (
|
|
99
|
-
return
|
|
99
|
+
if (el.getAttribute('data-rum-source')) {
|
|
100
|
+
return el.getAttribute('data-rum-source');
|
|
100
101
|
}
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
return `${
|
|
102
|
+
const ctx = getSourceContext(el.parentElement) || '';
|
|
103
|
+
const name = getSourceElement(el) || '';
|
|
104
|
+
const id = getSourceIdentifier(el) || '';
|
|
105
|
+
return `${ctx} ${name}${id}`.trim() || `"${el.textContent.substring(0, 10)}"`;
|
|
105
106
|
/* c8 ignore next 3 */
|
|
106
107
|
} catch (error) {
|
|
107
108
|
return null;
|
package/modules/index.js
CHANGED
|
@@ -25,14 +25,14 @@ import { fflags } from './fflags.js';
|
|
|
25
25
|
const { sampleRUM, queue, isSelected } = (window.hlx && window.hlx.rum) ? window.hlx.rum
|
|
26
26
|
/* c8 ignore next */ : {};
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// blocks mutation observer
|
|
30
29
|
// eslint-disable-next-line no-use-before-define, max-len
|
|
31
|
-
const
|
|
30
|
+
const blocksMO = window.MutationObserver ? new MutationObserver(blocksMCB)
|
|
32
31
|
/* c8 ignore next */ : {};
|
|
33
32
|
|
|
33
|
+
// media mutation observer
|
|
34
34
|
// eslint-disable-next-line no-use-before-define, max-len
|
|
35
|
-
const
|
|
35
|
+
const mediaMO = window.MutationObserver ? new MutationObserver(mediaMCB)
|
|
36
36
|
/* c8 ignore next */ : {};
|
|
37
37
|
|
|
38
38
|
function trackCheckpoint(checkpoint, data, t) {
|
|
@@ -114,6 +114,8 @@ function addNavigationTracking() {
|
|
|
114
114
|
const navigate = (source, type, redirectCount) => {
|
|
115
115
|
// target can be 'visible', 'hidden' (background tab) or 'prerendered' (speculation rules)
|
|
116
116
|
const payload = { source, target: document.visibilityState };
|
|
117
|
+
/* c8 ignore next 13 */
|
|
118
|
+
// prerendering cannot be tested yet with headless browsers
|
|
117
119
|
if (document.prerendering) {
|
|
118
120
|
// listen for "activation" of the current pre-rendered page
|
|
119
121
|
document.addEventListener('prerenderingchange', () => {
|
|
@@ -136,7 +138,7 @@ function addNavigationTracking() {
|
|
|
136
138
|
sampleRUM('enter', payload); // enter site
|
|
137
139
|
}
|
|
138
140
|
fflags.enabled('redirect', () => {
|
|
139
|
-
const from = new URLSearchParams(window.location.search).get('
|
|
141
|
+
const from = new URLSearchParams(window.location.search).get('redirect_from');
|
|
140
142
|
if (redirectCount || from) {
|
|
141
143
|
sampleRUM('redirect', { source: from, target: redirectCount || 1 });
|
|
142
144
|
}
|
|
@@ -144,10 +146,10 @@ function addNavigationTracking() {
|
|
|
144
146
|
};
|
|
145
147
|
|
|
146
148
|
new PerformanceObserver((list) => list
|
|
147
|
-
.getEntries().map((
|
|
149
|
+
.getEntries().map((e) => navigate(
|
|
148
150
|
window.hlx.referrer || document.referrer,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
e.type,
|
|
152
|
+
e.redirectCount,
|
|
151
153
|
)))
|
|
152
154
|
.observe({ type: 'navigation', buffered: true });
|
|
153
155
|
}
|
|
@@ -156,16 +158,16 @@ function addLoadResourceTracking() {
|
|
|
156
158
|
const observer = new PerformanceObserver((list) => {
|
|
157
159
|
try {
|
|
158
160
|
list.getEntries()
|
|
159
|
-
.filter((
|
|
160
|
-
.filter((
|
|
161
|
-
.filter((
|
|
162
|
-
.forEach((
|
|
163
|
-
sampleRUM('loadresource', { source:
|
|
161
|
+
.filter((e) => !e.responseStatus || e.responseStatus < 400)
|
|
162
|
+
.filter((e) => window.location.hostname === new URL(e.name).hostname)
|
|
163
|
+
.filter((e) => new URL(e.name).pathname.match('.*(\\.plain\\.html$|\\.json|graphql|api)'))
|
|
164
|
+
.forEach((e) => {
|
|
165
|
+
sampleRUM('loadresource', { source: e.name, target: Math.round(e.duration) });
|
|
164
166
|
});
|
|
165
167
|
list.getEntries()
|
|
166
|
-
.filter((
|
|
167
|
-
.forEach((
|
|
168
|
-
sampleRUM('missingresource', { source:
|
|
168
|
+
.filter((e) => e.responseStatus === 404)
|
|
169
|
+
.forEach((e) => {
|
|
170
|
+
sampleRUM('missingresource', { source: e.name, target: e.hostname });
|
|
169
171
|
});
|
|
170
172
|
/* c8 ignore next 3 */
|
|
171
173
|
} catch (error) {
|
|
@@ -175,24 +177,26 @@ function addLoadResourceTracking() {
|
|
|
175
177
|
observer.observe({ type: 'resource', buffered: true });
|
|
176
178
|
}
|
|
177
179
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
+
// activate blocks mutation observer
|
|
181
|
+
function activateBlocksMO() {
|
|
182
|
+
if (!blocksMO || blocksMO.active) {
|
|
180
183
|
return;
|
|
181
184
|
}
|
|
182
|
-
|
|
183
|
-
|
|
185
|
+
blocksMO.active = true;
|
|
186
|
+
blocksMO.observe(
|
|
184
187
|
document.body,
|
|
185
188
|
// eslint-disable-next-line object-curly-newline
|
|
186
189
|
{ subtree: true, attributes: true, attributeFilter: ['data-block-status'] },
|
|
187
190
|
);
|
|
188
191
|
}
|
|
189
192
|
|
|
190
|
-
|
|
191
|
-
|
|
193
|
+
// activate media mutation observer
|
|
194
|
+
function activateMediaMO() {
|
|
195
|
+
if (!mediaMO || mediaMO.active) {
|
|
192
196
|
return;
|
|
193
197
|
}
|
|
194
|
-
|
|
195
|
-
|
|
198
|
+
mediaMO.active = true;
|
|
199
|
+
mediaMO.observe(
|
|
196
200
|
document.body,
|
|
197
201
|
// eslint-disable-next-line object-curly-newline
|
|
198
202
|
{ subtree: true, attributes: false, childList: true },
|
|
@@ -204,16 +208,16 @@ function getIntersectionObsever(checkpoint) {
|
|
|
204
208
|
if (!window.IntersectionObserver) {
|
|
205
209
|
return null;
|
|
206
210
|
}
|
|
207
|
-
|
|
208
|
-
|
|
211
|
+
activateBlocksMO();
|
|
212
|
+
activateMediaMO();
|
|
209
213
|
const observer = new IntersectionObserver((entries) => {
|
|
210
214
|
try {
|
|
211
215
|
entries
|
|
212
|
-
.filter((
|
|
213
|
-
.forEach((
|
|
214
|
-
observer.unobserve(
|
|
215
|
-
const target = targetSelector(
|
|
216
|
-
const source = sourceSelector(
|
|
216
|
+
.filter((e) => e.isIntersecting)
|
|
217
|
+
.forEach((e) => {
|
|
218
|
+
observer.unobserve(e.target); // observe only once
|
|
219
|
+
const target = targetSelector(e.target);
|
|
220
|
+
const source = sourceSelector(e.target);
|
|
217
221
|
sampleRUM(checkpoint, { target, source });
|
|
218
222
|
});
|
|
219
223
|
/* c8 ignore next 3 */
|
|
@@ -245,11 +249,10 @@ function addViewMediaTracking(parent) {
|
|
|
245
249
|
}
|
|
246
250
|
|
|
247
251
|
function addFormTracking(parent) {
|
|
248
|
-
|
|
249
|
-
|
|
252
|
+
activateBlocksMO();
|
|
253
|
+
activateMediaMO();
|
|
250
254
|
parent.querySelectorAll('form').forEach((form) => {
|
|
251
|
-
form.
|
|
252
|
-
form.addEventListener('submit', formSubmitListener);
|
|
255
|
+
form.addEventListener('submit', (e) => sampleRUM('formsubmit', { target: targetSelector(e.target), source: sourceSelector(e.target) }), { once: true });
|
|
253
256
|
});
|
|
254
257
|
}
|
|
255
258
|
|
|
@@ -257,7 +260,8 @@ function addObserver(ck, fn, block) {
|
|
|
257
260
|
return DEFAULT_TRACKING_EVENTS.includes(ck) && fn(block);
|
|
258
261
|
}
|
|
259
262
|
|
|
260
|
-
|
|
263
|
+
// blocks mutation observer callback
|
|
264
|
+
function blocksMCB(mutations) {
|
|
261
265
|
// block specific mutations
|
|
262
266
|
mutations
|
|
263
267
|
.filter((m) => m.type === 'attributes' && m.attributeName === 'data-block-status')
|
|
@@ -268,7 +272,8 @@ function blocksMutationsCallback(mutations) {
|
|
|
268
272
|
});
|
|
269
273
|
}
|
|
270
274
|
|
|
271
|
-
|
|
275
|
+
// media mutation observer callback
|
|
276
|
+
function mediaMCB(mutations) {
|
|
272
277
|
// media mutations
|
|
273
278
|
mutations
|
|
274
279
|
.forEach((m) => {
|
package/modules/martech.js
CHANGED
|
@@ -19,7 +19,7 @@ export function addCookieConsentTracking(sampleRUM) {
|
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
let
|
|
22
|
+
let consentMO; // consent mutation observer
|
|
23
23
|
const trackShowConsent = () => {
|
|
24
24
|
const otsdk = document.querySelector('body > div#onetrust-consent-sdk');
|
|
25
25
|
if (otsdk) {
|
|
@@ -28,8 +28,8 @@ export function addCookieConsentTracking(sampleRUM) {
|
|
|
28
28
|
} else {
|
|
29
29
|
sampleRUM('consent', { source: 'onetrust', target: 'show' });
|
|
30
30
|
}
|
|
31
|
-
if (
|
|
32
|
-
|
|
31
|
+
if (consentMO) {
|
|
32
|
+
consentMO.disconnect();
|
|
33
33
|
}
|
|
34
34
|
return true;
|
|
35
35
|
}
|
|
@@ -38,11 +38,11 @@ export function addCookieConsentTracking(sampleRUM) {
|
|
|
38
38
|
|
|
39
39
|
if (!trackShowConsent()) {
|
|
40
40
|
// eslint-disable-next-line max-len
|
|
41
|
-
|
|
41
|
+
consentMO = window.MutationObserver
|
|
42
42
|
? new MutationObserver(trackShowConsent)
|
|
43
43
|
: /* c8 ignore next */ null;
|
|
44
|
-
if (
|
|
45
|
-
|
|
44
|
+
if (consentMO) {
|
|
45
|
+
consentMO.observe(
|
|
46
46
|
document.body,
|
|
47
47
|
// eslint-disable-next-line object-curly-newline
|
|
48
48
|
{ attributes: false, childList: true, subtree: false },
|
package/modules/utils.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
const { href } = window.location;
|
|
14
|
+
|
|
13
15
|
export const urlSanitizers = {
|
|
14
16
|
/**
|
|
15
17
|
* Returns the full url.
|
|
@@ -17,21 +19,21 @@ export const urlSanitizers = {
|
|
|
17
19
|
* @param {string} url (default: window.location.href) The url to sanitize
|
|
18
20
|
* @returns {string} The sanitized url
|
|
19
21
|
*/
|
|
20
|
-
full: (url =
|
|
22
|
+
full: (url = href) => new URL(url).toString(),
|
|
21
23
|
/**
|
|
22
24
|
* Returns the origin of the provided url.
|
|
23
25
|
* If no url is provided, it defaults to window.location.href.
|
|
24
26
|
* @param {string} url (default: window.location.href) The url to sanitize
|
|
25
27
|
* @returns {string} The sanitized url
|
|
26
28
|
*/
|
|
27
|
-
origin: (url =
|
|
29
|
+
origin: (url = href) => new URL(url).origin,
|
|
28
30
|
/**
|
|
29
31
|
* Returns the sanitized url: the origin and the path (no query params or hash)
|
|
30
32
|
* If no url is provided, it defaults to window.location.href.
|
|
31
33
|
* @param {string} url (default: window.location.href) The url to sanitize
|
|
32
34
|
* @returns {string} The sanitized url
|
|
33
35
|
*/
|
|
34
|
-
path: (url =
|
|
36
|
+
path: (url = href) => {
|
|
35
37
|
const u = new URL(url);
|
|
36
38
|
return `${u.origin}${u.pathname}`;
|
|
37
39
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/helix-rum-enhancer",
|
|
3
|
-
"version": "2.26.
|
|
3
|
+
"version": "2.26.1",
|
|
4
4
|
"description": "Helix RUM Enhancer",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"build-bundle": "rollup --config",
|
|
15
15
|
"prepack": "npm run build-bundle",
|
|
16
16
|
"semantic-release": "semantic-release",
|
|
17
|
-
"semantic-release-dry": "semantic-release --dry-run --branches $CI_BRANCH 1.x main
|
|
17
|
+
"semantic-release-dry": "semantic-release --dry-run --branches $CI_BRANCH 1.x main",
|
|
18
18
|
"prepare": "husky"
|
|
19
19
|
},
|
|
20
20
|
"repository": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@adobe/eslint-config-helix": "2.0.8",
|
|
37
|
-
"@adobe/helix-rum-js": "2.
|
|
37
|
+
"@adobe/helix-rum-js": "2.10.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",
|
|
@@ -48,19 +48,19 @@
|
|
|
48
48
|
"eslint": "8.57.1",
|
|
49
49
|
"eslint-plugin-header": "3.1.1",
|
|
50
50
|
"eslint-plugin-import": "2.31.0",
|
|
51
|
-
"husky": "9.1.
|
|
52
|
-
"jsdoc-to-markdown": "9.
|
|
51
|
+
"husky": "9.1.7",
|
|
52
|
+
"jsdoc-to-markdown": "9.1.1",
|
|
53
53
|
"junit-report-builder": "5.1.1",
|
|
54
54
|
"lint-staged": "15.2.10",
|
|
55
|
-
"mocha": "
|
|
55
|
+
"mocha": "11.0.1",
|
|
56
56
|
"mocha-multi-reporters": "1.5.1",
|
|
57
|
-
"rollup": "4.
|
|
57
|
+
"rollup": "4.28.1",
|
|
58
58
|
"rollup-plugin-checksum": "1.0.1",
|
|
59
59
|
"rollup-plugin-cleanup": "3.2.1",
|
|
60
60
|
"rollup-plugin-eslint-bundle": "9.0.0",
|
|
61
|
-
"semantic-release": "24.
|
|
61
|
+
"semantic-release": "24.2.0",
|
|
62
62
|
"semantic-release-slack-bot": "^4.0.2",
|
|
63
|
-
"web-vitals": "4.2.
|
|
63
|
+
"web-vitals": "4.2.4"
|
|
64
64
|
},
|
|
65
65
|
"lint-staged": {
|
|
66
66
|
"*.js": "eslint",
|
package/src/index.js
CHANGED
|
@@ -29,96 +29,98 @@
|
|
|
29
29
|
const DEFAULT_TRACKING_EVENTS = ['click', 'cwv', 'form', 'viewblock', 'viewmedia', 'loadresource', 'utm', 'paid', 'email', 'consent'];
|
|
30
30
|
fflags.enabled('example', () => DEFAULT_TRACKING_EVENTS.push('example'));
|
|
31
31
|
|
|
32
|
+
const { href } = window.location;
|
|
32
33
|
const urlSanitizers = {
|
|
33
|
-
full: (url =
|
|
34
|
-
origin: (url =
|
|
35
|
-
path: (url =
|
|
34
|
+
full: (url = href) => new URL(url).toString(),
|
|
35
|
+
origin: (url = href) => new URL(url).origin,
|
|
36
|
+
path: (url = href) => {
|
|
36
37
|
const u = new URL(url);
|
|
37
38
|
return `${u.origin}${u.pathname}`;
|
|
38
39
|
},
|
|
39
40
|
};
|
|
40
41
|
|
|
41
|
-
const getTargetValue = (
|
|
42
|
-
||
|
|
43
|
-
const targetSelector = (
|
|
42
|
+
const getTargetValue = (el) => el.getAttribute('data-rum-target') || el.getAttribute('href')
|
|
43
|
+
|| el.currentSrc || el.getAttribute('src') || el.dataset.action || el.action;
|
|
44
|
+
const targetSelector = (el) => {
|
|
44
45
|
try {
|
|
45
|
-
if (!
|
|
46
|
-
let
|
|
47
|
-
if (!
|
|
48
|
-
|
|
46
|
+
if (!el) return undefined;
|
|
47
|
+
let v = getTargetValue(el);
|
|
48
|
+
if (!v && el.tagName !== 'A' && el.closest('a')) {
|
|
49
|
+
v = getTargetValue(el.closest('a'));
|
|
49
50
|
}
|
|
50
|
-
if (
|
|
51
|
-
|
|
51
|
+
if (v && !v.startsWith('https://')) {
|
|
52
|
+
v = new URL(v, window.location).href;
|
|
52
53
|
}
|
|
53
|
-
return
|
|
54
|
+
return v;
|
|
54
55
|
} catch (error) {
|
|
55
56
|
return null;
|
|
56
57
|
}
|
|
57
58
|
};
|
|
58
|
-
function walk(
|
|
59
|
-
if (!
|
|
59
|
+
function walk(el, checkFn) {
|
|
60
|
+
if (!el || el === document.body || el === document.documentElement) {
|
|
60
61
|
return undefined;
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
return checkValue || walk(element.parentElement, checkFn);
|
|
63
|
+
return checkFn(el) || walk(el.parentElement, checkFn);
|
|
64
64
|
}
|
|
65
|
-
function isDialog(
|
|
66
|
-
if (
|
|
67
|
-
if (
|
|
68
|
-
if (
|
|
69
|
-
if (
|
|
70
|
-
const
|
|
71
|
-
return (
|
|
65
|
+
function isDialog(el) {
|
|
66
|
+
if (el.tagName === 'DIALOG') return true;
|
|
67
|
+
if (el.getAttribute('role') === 'dialog') return true;
|
|
68
|
+
if (el.getAttribute('role') === 'alertdialog') return true;
|
|
69
|
+
if (el.getAttribute('aria-modal') === 'true') return true;
|
|
70
|
+
const cs = window.getComputedStyle(el);
|
|
71
|
+
return (cs && cs.position === 'fixed' && cs.zIndex > 100);
|
|
72
72
|
}
|
|
73
|
-
function isButton(
|
|
74
|
-
if (
|
|
75
|
-
if (
|
|
76
|
-
if (
|
|
77
|
-
const classes = Array.from(
|
|
73
|
+
function isButton(el) {
|
|
74
|
+
if (el.tagName === 'BUTTON') return true;
|
|
75
|
+
if (el.tagName === 'INPUT' && el.getAttribute('type') === 'button') return true;
|
|
76
|
+
if (el.tagName === 'A') {
|
|
77
|
+
const classes = Array.from(el.classList);
|
|
78
78
|
return classes.some((className) => className.match(/button|cta/));
|
|
79
79
|
}
|
|
80
|
-
return
|
|
80
|
+
return el.getAttribute('role') === 'button';
|
|
81
81
|
}
|
|
82
|
-
function getSourceContext(
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
function getSourceContext(el) {
|
|
83
|
+
const formEl = el.closest('form');
|
|
84
|
+
if (formEl) {
|
|
85
|
+
return `form${formEl.id ? `#${formEl.id}` : ''}`;
|
|
85
86
|
}
|
|
86
|
-
const block =
|
|
87
|
+
const block = el.closest('.block[data-block-name]');
|
|
87
88
|
if (block) return `.${block.getAttribute('data-block-name')}`;
|
|
88
|
-
if (walk(
|
|
89
|
-
if (
|
|
90
|
-
if (
|
|
91
|
-
if (
|
|
92
|
-
if (
|
|
93
|
-
return (walk(
|
|
89
|
+
if (walk(el, isDialog)) return 'dialog';
|
|
90
|
+
if (el.closest('nav')) return 'nav';
|
|
91
|
+
if (el.closest('header')) return 'header';
|
|
92
|
+
if (el.closest('footer')) return 'footer';
|
|
93
|
+
if (el.closest('aside')) return 'aside';
|
|
94
|
+
return (walk(el, (e) => e.id && `#${e.id}`));
|
|
94
95
|
}
|
|
95
|
-
function getSourceElement(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
function getSourceElement(el) {
|
|
97
|
+
const f = el.closest('form');
|
|
98
|
+
if (f && Array.from(f.elements).includes(el)) {
|
|
99
|
+
return (el.tagName.toLowerCase()
|
|
100
|
+
+ (['INPUT', 'BUTTON'].includes(el.tagName)
|
|
101
|
+
? `[type='${el.getAttribute('type') || ''}']`
|
|
100
102
|
: ''));
|
|
101
103
|
}
|
|
102
|
-
if (walk(
|
|
103
|
-
return
|
|
104
|
+
if (walk(el, isButton)) return 'button';
|
|
105
|
+
return el.tagName.toLowerCase().match(/^(a|img|video)$/) && el.tagName.toLowerCase();
|
|
104
106
|
}
|
|
105
|
-
function getSourceIdentifier(
|
|
106
|
-
if (
|
|
107
|
-
if (
|
|
108
|
-
return (
|
|
107
|
+
function getSourceIdentifier(el) {
|
|
108
|
+
if (el.id) return `#${el.id}`;
|
|
109
|
+
if (el.getAttribute('data-block-name')) return `.${el.getAttribute('data-block-name')}`;
|
|
110
|
+
return (el.classList.length > 0 && `.${el.classList[0]}`);
|
|
109
111
|
}
|
|
110
|
-
const sourceSelector = (
|
|
112
|
+
const sourceSelector = (el) => {
|
|
111
113
|
try {
|
|
112
|
-
if (!
|
|
114
|
+
if (!el || el === document.body || el === document.documentElement) {
|
|
113
115
|
return undefined;
|
|
114
116
|
}
|
|
115
|
-
if (
|
|
116
|
-
return
|
|
117
|
+
if (el.getAttribute('data-rum-source')) {
|
|
118
|
+
return el.getAttribute('data-rum-source');
|
|
117
119
|
}
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
return `${
|
|
120
|
+
const ctx = getSourceContext(el.parentElement) || '';
|
|
121
|
+
const name = getSourceElement(el) || '';
|
|
122
|
+
const id = getSourceIdentifier(el) || '';
|
|
123
|
+
return `${ctx} ${name}${id}`.trim() || `"${el.textContent.substring(0, 10)}"`;
|
|
122
124
|
} catch (error) {
|
|
123
125
|
return null;
|
|
124
126
|
}
|
|
@@ -132,7 +134,7 @@
|
|
|
132
134
|
sampleRUM('consent', { source: 'onetrust', target: 'hidden' });
|
|
133
135
|
return;
|
|
134
136
|
}
|
|
135
|
-
let
|
|
137
|
+
let consentMO;
|
|
136
138
|
const trackShowConsent = () => {
|
|
137
139
|
const otsdk = document.querySelector('body > div#onetrust-consent-sdk');
|
|
138
140
|
if (otsdk) {
|
|
@@ -141,19 +143,19 @@
|
|
|
141
143
|
} else {
|
|
142
144
|
sampleRUM('consent', { source: 'onetrust', target: 'show' });
|
|
143
145
|
}
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
+
if (consentMO) {
|
|
147
|
+
consentMO.disconnect();
|
|
146
148
|
}
|
|
147
149
|
return true;
|
|
148
150
|
}
|
|
149
151
|
return false;
|
|
150
152
|
};
|
|
151
153
|
if (!trackShowConsent()) {
|
|
152
|
-
|
|
154
|
+
consentMO = window.MutationObserver
|
|
153
155
|
? new MutationObserver(trackShowConsent)
|
|
154
156
|
: null;
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
+
if (consentMO) {
|
|
158
|
+
consentMO.observe(
|
|
157
159
|
document.body,
|
|
158
160
|
{ attributes: false, childList: true, subtree: false },
|
|
159
161
|
);
|
|
@@ -197,10 +199,9 @@
|
|
|
197
199
|
|
|
198
200
|
const { sampleRUM, queue, isSelected } = (window.hlx && window.hlx.rum) ? window.hlx.rum
|
|
199
201
|
: {};
|
|
200
|
-
const
|
|
201
|
-
const blocksMutationObserver = window.MutationObserver ? new MutationObserver(blocksMutationsCallback)
|
|
202
|
+
const blocksMO = window.MutationObserver ? new MutationObserver(blocksMCB)
|
|
202
203
|
: {};
|
|
203
|
-
const
|
|
204
|
+
const mediaMO = window.MutationObserver ? new MutationObserver(mediaMCB)
|
|
204
205
|
: {};
|
|
205
206
|
function trackCheckpoint(checkpoint, data, t) {
|
|
206
207
|
const { weight, id } = window.hlx.rum;
|
|
@@ -286,17 +287,17 @@
|
|
|
286
287
|
sampleRUM('enter', payload);
|
|
287
288
|
}
|
|
288
289
|
fflags.enabled('redirect', () => {
|
|
289
|
-
const from = new URLSearchParams(window.location.search).get('
|
|
290
|
+
const from = new URLSearchParams(window.location.search).get('redirect_from');
|
|
290
291
|
if (redirectCount || from) {
|
|
291
292
|
sampleRUM('redirect', { source: from, target: redirectCount || 1 });
|
|
292
293
|
}
|
|
293
294
|
});
|
|
294
295
|
};
|
|
295
296
|
new PerformanceObserver((list) => list
|
|
296
|
-
.getEntries().map((
|
|
297
|
+
.getEntries().map((e) => navigate(
|
|
297
298
|
window.hlx.referrer || document.referrer,
|
|
298
|
-
|
|
299
|
-
|
|
299
|
+
e.type,
|
|
300
|
+
e.redirectCount,
|
|
300
301
|
)))
|
|
301
302
|
.observe({ type: 'navigation', buffered: true });
|
|
302
303
|
}
|
|
@@ -304,38 +305,38 @@
|
|
|
304
305
|
const observer = new PerformanceObserver((list) => {
|
|
305
306
|
try {
|
|
306
307
|
list.getEntries()
|
|
307
|
-
.filter((
|
|
308
|
-
.filter((
|
|
309
|
-
.filter((
|
|
310
|
-
.forEach((
|
|
311
|
-
sampleRUM('loadresource', { source:
|
|
308
|
+
.filter((e) => !e.responseStatus || e.responseStatus < 400)
|
|
309
|
+
.filter((e) => window.location.hostname === new URL(e.name).hostname)
|
|
310
|
+
.filter((e) => new URL(e.name).pathname.match('.*(\\.plain\\.html$|\\.json|graphql|api)'))
|
|
311
|
+
.forEach((e) => {
|
|
312
|
+
sampleRUM('loadresource', { source: e.name, target: Math.round(e.duration) });
|
|
312
313
|
});
|
|
313
314
|
list.getEntries()
|
|
314
|
-
.filter((
|
|
315
|
-
.forEach((
|
|
316
|
-
sampleRUM('missingresource', { source:
|
|
315
|
+
.filter((e) => e.responseStatus === 404)
|
|
316
|
+
.forEach((e) => {
|
|
317
|
+
sampleRUM('missingresource', { source: e.name, target: e.hostname });
|
|
317
318
|
});
|
|
318
319
|
} catch (error) {
|
|
319
320
|
}
|
|
320
321
|
});
|
|
321
322
|
observer.observe({ type: 'resource', buffered: true });
|
|
322
323
|
}
|
|
323
|
-
function
|
|
324
|
-
if (!
|
|
324
|
+
function activateBlocksMO() {
|
|
325
|
+
if (!blocksMO || blocksMO.active) {
|
|
325
326
|
return;
|
|
326
327
|
}
|
|
327
|
-
|
|
328
|
-
|
|
328
|
+
blocksMO.active = true;
|
|
329
|
+
blocksMO.observe(
|
|
329
330
|
document.body,
|
|
330
331
|
{ subtree: true, attributes: true, attributeFilter: ['data-block-status'] },
|
|
331
332
|
);
|
|
332
333
|
}
|
|
333
|
-
function
|
|
334
|
-
if (!
|
|
334
|
+
function activateMediaMO() {
|
|
335
|
+
if (!mediaMO || mediaMO.active) {
|
|
335
336
|
return;
|
|
336
337
|
}
|
|
337
|
-
|
|
338
|
-
|
|
338
|
+
mediaMO.active = true;
|
|
339
|
+
mediaMO.observe(
|
|
339
340
|
document.body,
|
|
340
341
|
{ subtree: true, attributes: false, childList: true },
|
|
341
342
|
);
|
|
@@ -344,16 +345,16 @@
|
|
|
344
345
|
if (!window.IntersectionObserver) {
|
|
345
346
|
return null;
|
|
346
347
|
}
|
|
347
|
-
|
|
348
|
-
|
|
348
|
+
activateBlocksMO();
|
|
349
|
+
activateMediaMO();
|
|
349
350
|
const observer = new IntersectionObserver((entries) => {
|
|
350
351
|
try {
|
|
351
352
|
entries
|
|
352
|
-
.filter((
|
|
353
|
-
.forEach((
|
|
354
|
-
observer.unobserve(
|
|
355
|
-
const target = targetSelector(
|
|
356
|
-
const source = sourceSelector(
|
|
353
|
+
.filter((e) => e.isIntersecting)
|
|
354
|
+
.forEach((e) => {
|
|
355
|
+
observer.unobserve(e.target);
|
|
356
|
+
const target = targetSelector(e.target);
|
|
357
|
+
const source = sourceSelector(e.target);
|
|
357
358
|
sampleRUM(checkpoint, { target, source });
|
|
358
359
|
});
|
|
359
360
|
} catch (error) {
|
|
@@ -381,17 +382,16 @@
|
|
|
381
382
|
}
|
|
382
383
|
}
|
|
383
384
|
function addFormTracking(parent) {
|
|
384
|
-
|
|
385
|
-
|
|
385
|
+
activateBlocksMO();
|
|
386
|
+
activateMediaMO();
|
|
386
387
|
parent.querySelectorAll('form').forEach((form) => {
|
|
387
|
-
form.
|
|
388
|
-
form.addEventListener('submit', formSubmitListener);
|
|
388
|
+
form.addEventListener('submit', (e) => sampleRUM('formsubmit', { target: targetSelector(e.target), source: sourceSelector(e.target) }), { once: true });
|
|
389
389
|
});
|
|
390
390
|
}
|
|
391
391
|
function addObserver(ck, fn, block) {
|
|
392
392
|
return DEFAULT_TRACKING_EVENTS.includes(ck) && fn(block);
|
|
393
393
|
}
|
|
394
|
-
function
|
|
394
|
+
function blocksMCB(mutations) {
|
|
395
395
|
mutations
|
|
396
396
|
.filter((m) => m.type === 'attributes' && m.attributeName === 'data-block-status')
|
|
397
397
|
.filter((m) => m.target.dataset.blockStatus === 'loaded')
|
|
@@ -400,7 +400,7 @@
|
|
|
400
400
|
addObserver('viewblock', addViewBlockTracking, m.target);
|
|
401
401
|
});
|
|
402
402
|
}
|
|
403
|
-
function
|
|
403
|
+
function mediaMCB(mutations) {
|
|
404
404
|
mutations
|
|
405
405
|
.forEach((m) => {
|
|
406
406
|
addObserver('viewmedia', addViewMediaTracking, m.target);
|
package/src/index.md5
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
9516743215eadcd19f5b5cf311883d16
|
|
@@ -23,6 +23,7 @@ export default {
|
|
|
23
23
|
'test/fixtures/**',
|
|
24
24
|
'node_modules/**',
|
|
25
25
|
'.rum/**',
|
|
26
|
+
'src/**',
|
|
26
27
|
],
|
|
27
28
|
},
|
|
28
29
|
files: [
|
|
@@ -43,9 +44,10 @@ export default {
|
|
|
43
44
|
context.url = '/node_modules/@adobe/helix-rum-js/dist/rum-standalone.js';
|
|
44
45
|
await next();
|
|
45
46
|
context.body = context.body
|
|
46
|
-
.replace(/const weight
|
|
47
|
+
.replace(/const weight =/, 'const weight = 1 ||')
|
|
47
48
|
.replace(/navigator\.sendBeacon/g, 'fakeSendBeacon')
|
|
48
|
-
|
|
49
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
50
|
+
.replace('.rum/@adobe/helix-rum-enhancer@${enhancerVersion || \'^2\'}/src/index.js', 'src/index.map.js');
|
|
49
51
|
return true;
|
|
50
52
|
} else if (context.url.startsWith('/.rum/web-vitals')) {
|
|
51
53
|
context.url = '/node_modules/web-vitals/dist/web-vitals.iife.js';
|