@adobe/helix-rum-enhancer 2.14.0 → 2.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.husky/pre-commit +1 -0
- package/CHANGELOG.md +14 -0
- package/modules/defaults.js +13 -0
- package/modules/dom.js +66 -0
- package/modules/fflags.js +21 -0
- package/modules/index.js +345 -0
- package/modules/utils.js +38 -0
- package/package.json +4 -4
- package/rollup.config.js +8 -2
- package/src/index.js +379 -359
- package/dist/rum-enhancer.js +0 -416
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm test
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [2.14.2](https://github.com/adobe/helix-rum-enhancer/compare/v2.14.1...v2.14.2) (2024-06-12)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* restore original path behavior ([#201](https://github.com/adobe/helix-rum-enhancer/issues/201)) ([f70df99](https://github.com/adobe/helix-rum-enhancer/commit/f70df992da778f5700d9bdc8b4f114ddf272ea77))
|
|
7
|
+
|
|
8
|
+
## [2.14.1](https://github.com/adobe/helix-rum-enhancer/compare/v2.14.0...v2.14.1) (2024-06-11)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* re-release after switching build infra ([95a8000](https://github.com/adobe/helix-rum-enhancer/commit/95a800050557cbdec7f70fac1f4784b512ba27de))
|
|
14
|
+
|
|
1
15
|
# [2.14.0](https://github.com/adobe/helix-rum-enhancer/compare/v2.13.0...v2.14.0) (2024-06-11)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
export const KNOWN_PROPERTIES = ['weight', 'id', 'referer', 'checkpoint', 't', 'source', 'target', 'cwv', 'CLS', 'FID', 'LCP', 'INP', 'TTFB'];
|
|
13
|
+
export const DEFAULT_TRACKING_EVENTS = ['click', 'cwv', 'form', 'enterleave', 'viewblock', 'viewmedia', 'loadresource', 'utm'];
|
package/modules/dom.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
export const getTargetValue = (element) => element.getAttribute('data-rum-target') || element.getAttribute('href')
|
|
13
|
+
|| element.currentSrc || element.getAttribute('src') || element.dataset.action || element.action;
|
|
14
|
+
|
|
15
|
+
export const targetSelector = (element) => {
|
|
16
|
+
try {
|
|
17
|
+
if (!element) return undefined;
|
|
18
|
+
let value = getTargetValue(element);
|
|
19
|
+
if (!value && element.tagName !== 'A' && element.closest('a')) {
|
|
20
|
+
value = getTargetValue(element.closest('a'));
|
|
21
|
+
}
|
|
22
|
+
if (value && !value.startsWith('https://')) {
|
|
23
|
+
// resolve relative links
|
|
24
|
+
value = new URL(value, window.location).href;
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
/* c8 ignore next 3 */
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const sourceSelector = (element) => {
|
|
34
|
+
try {
|
|
35
|
+
if (!element || element === document.body || element === document.documentElement) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
if (element.getAttribute('data-rum-source')) {
|
|
39
|
+
return element.getAttribute('data-rum-source');
|
|
40
|
+
}
|
|
41
|
+
const form = element.closest('form');
|
|
42
|
+
let formElementSelector = '';
|
|
43
|
+
if (form && Array.from(form.elements).includes(element)) {
|
|
44
|
+
formElementSelector = element.tagName === 'INPUT' ? `form input[type='${element.getAttribute('type')}']` : `form ${element.tagName.toLowerCase()}`;
|
|
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
|
+
if (Array.from(element.classList).some((className) => className.match(/button|cta/))) {
|
|
58
|
+
return blockName ? `.${blockName} .button` : '.button';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return sourceSelector(element.parentElement);
|
|
62
|
+
/* c8 ignore next 3 */
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
export const fflags = {
|
|
13
|
+
has: (flag) => fflags[flag].indexOf(Array.from(window.origin)
|
|
14
|
+
.map((a) => a.charCodeAt(0))
|
|
15
|
+
.reduce((a, b) => a + b, 1) % 1371) !== -1,
|
|
16
|
+
enabled: (flag, callback) => fflags.has(flag) && callback(),
|
|
17
|
+
disabled: (flag, callback) => !fflags.has(flag) && callback(),
|
|
18
|
+
onetrust: [543, 770, 1136],
|
|
19
|
+
ads: [1139, 543],
|
|
20
|
+
email: [1139, 543],
|
|
21
|
+
};
|
package/modules/index.js
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
/* eslint-env browser */
|
|
13
|
+
|
|
14
|
+
import { KNOWN_PROPERTIES, DEFAULT_TRACKING_EVENTS } from './defaults.js';
|
|
15
|
+
import { fflags } from './fflags.js';
|
|
16
|
+
import { urlSanitizers } from './utils.js';
|
|
17
|
+
import { targetSelector, sourceSelector } from './dom.js';
|
|
18
|
+
|
|
19
|
+
const { sampleRUM, queue, isSelected } = (window.hlx && window.hlx.rum) ? window.hlx.rum : {};
|
|
20
|
+
|
|
21
|
+
const formSubmitListener = (e) => sampleRUM('formsubmit', { target: targetSelector(e.target), source: sourceSelector(e.target) });
|
|
22
|
+
// eslint-disable-next-line no-use-before-define
|
|
23
|
+
const mutationObserver = window.MutationObserver ? new MutationObserver(mutationsCallback) : null;
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line no-unused-vars
|
|
26
|
+
function optedIn(checkpoint, data) {
|
|
27
|
+
// TODO: check config service to know if
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
// Gets configured collection from the config service for the current domain
|
|
31
|
+
function getCollectionConfig() {
|
|
32
|
+
// eslint-disable-next-line max-len
|
|
33
|
+
fflags.enabled('onetrust', () => DEFAULT_TRACKING_EVENTS.push('consent'));
|
|
34
|
+
fflags.enabled('ads', () => DEFAULT_TRACKING_EVENTS.push('paid'));
|
|
35
|
+
fflags.enabled('email', () => DEFAULT_TRACKING_EVENTS.push('email'));
|
|
36
|
+
return DEFAULT_TRACKING_EVENTS;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function trackCheckpoint(checkpoint, data, t) {
|
|
40
|
+
const { weight, id } = window.hlx.rum;
|
|
41
|
+
if (optedIn(checkpoint, data) && isSelected) {
|
|
42
|
+
const sendPing = (pdata = data) => {
|
|
43
|
+
// eslint-disable-next-line object-curly-newline, max-len
|
|
44
|
+
const body = JSON.stringify({ weight, id, referer: urlSanitizers[window.hlx.RUM_MASK_URL || 'path'](), checkpoint, t, ...data }, KNOWN_PROPERTIES);
|
|
45
|
+
const { href: url, origin } = new URL(`.rum/${weight}`, sampleRUM.collectBaseURL || sampleRUM.baseURL);
|
|
46
|
+
if (window.location.origin === origin) {
|
|
47
|
+
const headers = { type: 'application/json' };
|
|
48
|
+
navigator.sendBeacon(url, new Blob([body], headers));
|
|
49
|
+
} else {
|
|
50
|
+
navigator.sendBeacon(url, body);
|
|
51
|
+
}
|
|
52
|
+
// eslint-disable-next-line no-console
|
|
53
|
+
console.debug(`ping:${checkpoint}`, pdata);
|
|
54
|
+
};
|
|
55
|
+
sendPing(data);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function processQueue() {
|
|
60
|
+
while (queue.length) {
|
|
61
|
+
const ck = queue.shift();
|
|
62
|
+
trackCheckpoint(...ck);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function addCWVTracking() {
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
try {
|
|
69
|
+
const cwvScript = new URL('.rum/web-vitals/dist/web-vitals.iife.js', sampleRUM.baseURL).href;
|
|
70
|
+
if (document.querySelector(`script[src="${cwvScript}"]`)) {
|
|
71
|
+
// web vitals script has been loaded already
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const script = document.createElement('script');
|
|
75
|
+
script.src = cwvScript;
|
|
76
|
+
script.onload = () => {
|
|
77
|
+
const storeCWV = (measurement) => {
|
|
78
|
+
const data = { cwv: {} };
|
|
79
|
+
data.cwv[measurement.name] = measurement.value;
|
|
80
|
+
if (measurement.name === 'LCP' && measurement.entries.length > 0) {
|
|
81
|
+
const { element } = measurement.entries.pop();
|
|
82
|
+
data.target = targetSelector(element);
|
|
83
|
+
data.source = sourceSelector(element) || (element && element.outerHTML.slice(0, 30));
|
|
84
|
+
}
|
|
85
|
+
sampleRUM('cwv', data);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const featureToggle = () => window.location.hostname === 'blog.adobe.com';
|
|
89
|
+
const isEager = (metric) => ['CLS', 'LCP'].includes(metric);
|
|
90
|
+
|
|
91
|
+
// When loading `web-vitals` using a classic script, all the public
|
|
92
|
+
// methods can be found on the `webVitals` global namespace.
|
|
93
|
+
['FID', 'INP', 'TTFB', 'CLS', 'LCP'].forEach((metric) => {
|
|
94
|
+
const metricFn = window.webVitals[`on${metric}`];
|
|
95
|
+
if (typeof metricFn === 'function') {
|
|
96
|
+
const opts = isEager(metric) ? { reportAllChanges: featureToggle() } : undefined;
|
|
97
|
+
metricFn(storeCWV, opts);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
document.head.appendChild(script);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
// something went wrong
|
|
104
|
+
}
|
|
105
|
+
}, 2000); // wait for delayed
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function addEnterLeaveTracking() {
|
|
109
|
+
// enter checkpoint when referrer is not the current page url
|
|
110
|
+
const navigate = (source, type) => {
|
|
111
|
+
const payload = { source, target: document.visibilityState };
|
|
112
|
+
// reload: same page, navigate: same origin, enter: everything else
|
|
113
|
+
if (type === 'reload' || source === window.location.href) {
|
|
114
|
+
sampleRUM('reload', payload);
|
|
115
|
+
} else if (type && type !== 'navigate') {
|
|
116
|
+
sampleRUM(type, payload); // back, forward, prerender, etc.
|
|
117
|
+
} else if (source && window.location.origin === new URL(source).origin) {
|
|
118
|
+
sampleRUM('navigate', payload); // internal navigation
|
|
119
|
+
} else {
|
|
120
|
+
sampleRUM('enter', payload); // enter site
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
new PerformanceObserver((list) => list
|
|
125
|
+
.getEntries().map((entry) => navigate(document.referrer, entry.type)))
|
|
126
|
+
.observe({ type: 'navigation', buffered: true });
|
|
127
|
+
|
|
128
|
+
const leave = ((event) => {
|
|
129
|
+
try {
|
|
130
|
+
if (leave.left || (event.type === 'visibilitychange' && document.visibilityState !== 'hidden')) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
leave.left = true;
|
|
134
|
+
sampleRUM('leave');
|
|
135
|
+
} catch (error) {
|
|
136
|
+
// something went wrong
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
window.addEventListener('visibilitychange', ((event) => leave(event)));
|
|
140
|
+
window.addEventListener('pagehide', ((event) => leave(event)));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function addLoadResourceTracking() {
|
|
144
|
+
const observer = new PerformanceObserver((list) => {
|
|
145
|
+
try {
|
|
146
|
+
list.getEntries()
|
|
147
|
+
.filter((entry) => !entry.responseStatus || entry.responseStatus < 400)
|
|
148
|
+
.filter((entry) => window.location.hostname === new URL(entry.name).hostname)
|
|
149
|
+
.filter((entry) => new URL(entry.name).pathname.match('.*(\\.plain\\.html|\\.json)$'))
|
|
150
|
+
.forEach((entry) => {
|
|
151
|
+
sampleRUM('loadresource', { source: entry.name, target: Math.round(entry.duration) });
|
|
152
|
+
});
|
|
153
|
+
list.getEntries()
|
|
154
|
+
.filter((entry) => entry.responseStatus === 404)
|
|
155
|
+
.forEach((entry) => {
|
|
156
|
+
sampleRUM('missingresource', { source: entry.name, target: entry.hostname });
|
|
157
|
+
});
|
|
158
|
+
} catch (error) {
|
|
159
|
+
// something went wrong
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
observer.observe({ type: 'resource', buffered: true });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function activateBlocksMutationObserver() {
|
|
166
|
+
if (!mutationObserver || mutationObserver.active) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
mutationObserver.active = true;
|
|
170
|
+
mutationObserver.observe(
|
|
171
|
+
document.body,
|
|
172
|
+
// eslint-disable-next-line object-curly-newline
|
|
173
|
+
{ subtree: true, attributes: true, attributeFilter: ['data-block-status'] },
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getIntersectionObsever(checkpoint) {
|
|
178
|
+
if (!window.IntersectionObserver) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
activateBlocksMutationObserver();
|
|
182
|
+
const observer = new IntersectionObserver((entries) => {
|
|
183
|
+
try {
|
|
184
|
+
entries
|
|
185
|
+
.filter((entry) => entry.isIntersecting)
|
|
186
|
+
.forEach((entry) => {
|
|
187
|
+
observer.unobserve(entry.target); // observe only once
|
|
188
|
+
const target = targetSelector(entry.target);
|
|
189
|
+
const source = sourceSelector(entry.target);
|
|
190
|
+
sampleRUM(checkpoint, { target, source });
|
|
191
|
+
});
|
|
192
|
+
} catch (error) {
|
|
193
|
+
// something went wrong
|
|
194
|
+
}
|
|
195
|
+
}, { threshold: 0.25 });
|
|
196
|
+
return observer;
|
|
197
|
+
}
|
|
198
|
+
function addViewBlockTracking(element) {
|
|
199
|
+
const blockobserver = getIntersectionObsever('viewblock');
|
|
200
|
+
if (blockobserver) {
|
|
201
|
+
const blocks = element.getAttribute('data-block-status') ? [element] : element.querySelectorAll('div[data-block-status="loaded"]');
|
|
202
|
+
blocks.forEach((b) => blockobserver.observe(b));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function addViewMediaTracking(parent) {
|
|
207
|
+
const mediaobserver = getIntersectionObsever('viewmedia');
|
|
208
|
+
if (mediaobserver) {
|
|
209
|
+
parent.querySelectorAll('img, video, audio, iframe').forEach((m) => {
|
|
210
|
+
if (!m.closest('div .block') || m.closest('div[data-block-status="loaded"]')) {
|
|
211
|
+
mediaobserver.observe(m);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function addUTMParametersTracking() {
|
|
218
|
+
const usp = new URLSearchParams(window.location.search);
|
|
219
|
+
[...usp.entries()]
|
|
220
|
+
.filter(([key]) => key.startsWith('utm_'))
|
|
221
|
+
// exclude keys that may leak PII
|
|
222
|
+
.filter(([key]) => key !== 'utm_id')
|
|
223
|
+
.filter(([key]) => key !== 'utm_term')
|
|
224
|
+
.forEach(([source, target]) => sampleRUM('utm', { source, target }));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function addAdsParametersTracking() {
|
|
228
|
+
const networks = {
|
|
229
|
+
google: /gclid|gclsrc|wbraid|gbraid/,
|
|
230
|
+
doubleclick: /dclid/,
|
|
231
|
+
microsoft: /msclkid/,
|
|
232
|
+
facebook: /fb(cl|ad_|pxl_)id/,
|
|
233
|
+
twitter: /tw(clid|src|term)/,
|
|
234
|
+
linkedin: /li_fat_id/,
|
|
235
|
+
pinterest: /epik/,
|
|
236
|
+
tiktok: /ttclid/,
|
|
237
|
+
};
|
|
238
|
+
const params = Array.from(new URLSearchParams(window.location.search).keys());
|
|
239
|
+
Object.entries(networks).forEach(([network, regex]) => {
|
|
240
|
+
params.filter((param) => regex.test(param)).forEach((param) => sampleRUM('paid', { source: network, target: param }));
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function addEmailParameterTracking() {
|
|
245
|
+
const networks = {
|
|
246
|
+
mailchimp: /mc_(c|e)id/,
|
|
247
|
+
marketo: /mkt_tok/,
|
|
248
|
+
|
|
249
|
+
};
|
|
250
|
+
const params = Array.from(new URLSearchParams(window.location.search).keys());
|
|
251
|
+
Object.entries(networks).forEach(([network, regex]) => {
|
|
252
|
+
params.filter((param) => regex.test(param)).forEach((param) => sampleRUM('email', { source: network, target: param }));
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function addFormTracking(parent) {
|
|
257
|
+
activateBlocksMutationObserver();
|
|
258
|
+
parent.querySelectorAll('form').forEach((form) => {
|
|
259
|
+
form.removeEventListener('submit', formSubmitListener); // listen only once
|
|
260
|
+
form.addEventListener('submit', formSubmitListener);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function addCookieConsentTracking() {
|
|
265
|
+
const cmpCookie = document.cookie.split(';')
|
|
266
|
+
.map((c) => c.trim())
|
|
267
|
+
.find((cookie) => cookie.startsWith('OptanonAlertBoxClosed='));
|
|
268
|
+
|
|
269
|
+
if (cmpCookie) {
|
|
270
|
+
sampleRUM('consent', { source: 'onetrust', target: 'hidden' });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let consentMutationObserver;
|
|
275
|
+
const trackShowConsent = () => {
|
|
276
|
+
if (document.querySelector('body > div#onetrust-consent-sdk')) {
|
|
277
|
+
sampleRUM('consent', { source: 'onetrust', target: 'show' });
|
|
278
|
+
if (consentMutationObserver) {
|
|
279
|
+
consentMutationObserver.disconnect();
|
|
280
|
+
}
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
return false;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
if (!trackShowConsent()) {
|
|
287
|
+
// eslint-disable-next-line max-len
|
|
288
|
+
consentMutationObserver = window.MutationObserver ? new MutationObserver(trackShowConsent) : null;
|
|
289
|
+
if (consentMutationObserver) {
|
|
290
|
+
consentMutationObserver.observe(
|
|
291
|
+
document.body,
|
|
292
|
+
// eslint-disable-next-line object-curly-newline
|
|
293
|
+
{ attributes: false, childList: true, subtree: false },
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const addObserver = (ck, fn, block) => getCollectionConfig().includes(ck) && fn(block);
|
|
300
|
+
function mutationsCallback(mutations) {
|
|
301
|
+
mutations.filter((m) => m.type === 'attributes' && m.attributeName === 'data-block-status')
|
|
302
|
+
.filter((m) => m.target.dataset.blockStatus === 'loaded')
|
|
303
|
+
.forEach((m) => {
|
|
304
|
+
addObserver('form', addFormTracking, m.target);
|
|
305
|
+
addObserver('viewblock', addViewBlockTracking, m.target);
|
|
306
|
+
addObserver('viewmedia', addViewMediaTracking, m.target);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function addTrackingFromConfig() {
|
|
311
|
+
const trackingFunctions = {
|
|
312
|
+
click: () => {
|
|
313
|
+
document.addEventListener('click', (event) => {
|
|
314
|
+
sampleRUM('click', { target: targetSelector(event.target), source: sourceSelector(event.target) });
|
|
315
|
+
});
|
|
316
|
+
},
|
|
317
|
+
cwv: () => addCWVTracking(),
|
|
318
|
+
form: () => addFormTracking(window.document.body),
|
|
319
|
+
enterleave: () => addEnterLeaveTracking(),
|
|
320
|
+
loadresource: () => addLoadResourceTracking(),
|
|
321
|
+
utm: () => addUTMParametersTracking(),
|
|
322
|
+
viewblock: () => addViewBlockTracking(window.document.body),
|
|
323
|
+
viewmedia: () => addViewMediaTracking(window.document.body),
|
|
324
|
+
consent: () => addCookieConsentTracking(),
|
|
325
|
+
paid: () => addAdsParametersTracking(),
|
|
326
|
+
email: () => addEmailParameterTracking(),
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
getCollectionConfig().filter((ck) => trackingFunctions[ck])
|
|
330
|
+
.forEach((ck) => trackingFunctions[ck]());
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function initEnhancer() {
|
|
334
|
+
try {
|
|
335
|
+
if (sampleRUM) {
|
|
336
|
+
addTrackingFromConfig();
|
|
337
|
+
window.hlx.rum.collector = trackCheckpoint;
|
|
338
|
+
processQueue();
|
|
339
|
+
}
|
|
340
|
+
} catch (error) {
|
|
341
|
+
// something went wrong
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
initEnhancer();
|
package/modules/utils.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const urlSanitizers = {
|
|
14
|
+
/**
|
|
15
|
+
* Returns the full url.
|
|
16
|
+
* If no url is provided, it defaults to window.location.href.
|
|
17
|
+
* @param {string} url (default: window.location.href) The url to sanitize
|
|
18
|
+
* @returns {string} The sanitized url
|
|
19
|
+
*/
|
|
20
|
+
full: (url = window.location.href) => new URL(url).toString(),
|
|
21
|
+
/**
|
|
22
|
+
* Returns the origin of the provided url.
|
|
23
|
+
* If no url is provided, it defaults to window.location.href.
|
|
24
|
+
* @param {string} url (default: window.location.href) The url to sanitize
|
|
25
|
+
* @returns {string} The sanitized url
|
|
26
|
+
*/
|
|
27
|
+
origin: (url = window.location.href) => new URL(url).origin,
|
|
28
|
+
/**
|
|
29
|
+
* Returns the sanitized url: the origin and the path (no query params or hash)
|
|
30
|
+
* If no url is provided, it defaults to window.location.href.
|
|
31
|
+
* @param {string} url (default: window.location.href) The url to sanitize
|
|
32
|
+
* @returns {string} The sanitized url
|
|
33
|
+
*/
|
|
34
|
+
path: (url = window.location.href) => {
|
|
35
|
+
const u = new URL(url);
|
|
36
|
+
return `${u.origin}${u.pathname}`;
|
|
37
|
+
},
|
|
38
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/helix-rum-enhancer",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.2",
|
|
4
4
|
"description": "Helix RUM Enhancer",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"setup-playwright": "npx playwright install --with-deps chromium firefox webkit",
|
|
9
9
|
"test": "npm run setup-playwright && web-test-runner --node-resolve --coverage --playwright --browsers chromium firefox webkit",
|
|
10
10
|
"test:ci": "npm run setup-playwright && web-test-runner --node-resolve --coverage --playwright --browsers chromium firefox webkit",
|
|
11
|
-
"test:watch": "web-test-runner --node-resolve --coverage --watch",
|
|
11
|
+
"test:watch": "web-test-runner --node-resolve --playwright --coverage --watch",
|
|
12
12
|
"lint": "eslint .",
|
|
13
13
|
"docs": "npx jsdoc2md -c .jsdoc.json --files 'src/*.js' > docs/API.md",
|
|
14
14
|
"build-bundle": "rollup --config",
|
|
15
15
|
"prepack": "npm run build-bundle",
|
|
16
16
|
"semantic-release": "semantic-release",
|
|
17
17
|
"semantic-release-dry": "semantic-release --dry-run --branches $CI_BRANCH 1.x main",
|
|
18
|
-
"prepare": "husky
|
|
18
|
+
"prepare": "husky"
|
|
19
19
|
},
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/adobe/helix-rum-enhancer#readme",
|
|
30
30
|
"mocha": {
|
|
31
|
-
"require": "test/setup-env.js",
|
|
32
31
|
"recursive": "true",
|
|
33
32
|
"reporter": "mocha-multi-reporters",
|
|
34
33
|
"reporter-options": "configFile=.mocha-multi.json"
|
|
@@ -56,6 +55,7 @@
|
|
|
56
55
|
"mocha-multi-reporters": "1.5.1",
|
|
57
56
|
"rollup": "4.18.0",
|
|
58
57
|
"rollup-plugin-cleanup": "3.2.1",
|
|
58
|
+
"rollup-plugin-eslint-bundle": "9.0.0",
|
|
59
59
|
"semantic-release": "24.0.0"
|
|
60
60
|
},
|
|
61
61
|
"lint-staged": {
|
package/rollup.config.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import cleanup from 'rollup-plugin-cleanup';
|
|
14
|
+
import eslint from 'rollup-plugin-eslint-bundle';
|
|
14
15
|
|
|
15
16
|
const banner = `/*
|
|
16
17
|
* Copyright 2024 Adobe. All rights reserved.
|
|
@@ -29,8 +30,8 @@ const banner = `/*
|
|
|
29
30
|
|
|
30
31
|
const bundles = [
|
|
31
32
|
{
|
|
32
|
-
source: '
|
|
33
|
-
outputFile: '
|
|
33
|
+
source: 'modules/index.js',
|
|
34
|
+
outputFile: 'src/index',
|
|
34
35
|
},
|
|
35
36
|
];
|
|
36
37
|
|
|
@@ -50,5 +51,10 @@ export default [...bundles.map(({ outputFile, source }) => ({
|
|
|
50
51
|
comments: ['eslint', 'jsdoc', /^\//, /^\*(?!\sc8\s)(?!\n \* Copyright)/],
|
|
51
52
|
maxEmptyLines: -1,
|
|
52
53
|
}),
|
|
54
|
+
eslint({
|
|
55
|
+
eslintOptions: {
|
|
56
|
+
fix: true,
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
53
59
|
],
|
|
54
60
|
}))];
|