diff --git a/.storybook/main.js b/.storybook/main.js
index d1776b26..eb6f4aaf 100644
--- a/.storybook/main.js
+++ b/.storybook/main.js
@@ -7,7 +7,7 @@ module.exports = {
name: '@storybook/addon-coverage',
options: {
istanbul: {
- exclude: ['**/lib-franklin.js', '**/scripts.js']
+ exclude: ['**/aem.js', '**/scripts.js']
}
}
}, '@storybook/addon-mdx-gfm'],
diff --git a/404.html b/404.html
index c21fc249..291959fe 100644
--- a/404.html
+++ b/404.html
@@ -11,7 +11,7 @@
+
diff --git a/scripts/lib-franklin.js b/scripts/aem.js
similarity index 53%
rename from scripts/lib-franklin.js
rename to scripts/aem.js
index 4a568001..af8f44a9 100644
--- a/scripts/lib-franklin.js
+++ b/scripts/aem.js
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Adobe. All rights reserved.
+ * Copyright 2023 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
@@ -10,16 +10,21 @@
* governing permissions and limitations under the License.
*/
+/* eslint-env browser */
+
/**
* log RUM if part of the sample.
* @param {string} checkpoint identifies the checkpoint in funnel
* @param {Object} data additional data for RUM sample
+ * @param {string} data.source DOM node that is the source of a checkpoint event,
+ * identified by #id or .classname
+ * @param {string} data.target subject of the checkpoint event,
+ * for instance the href of a link, or a search term
*/
-export function sampleRUM(checkpoint, data = {}) {
+function sampleRUM(checkpoint, data = {}) {
sampleRUM.defer = sampleRUM.defer || [];
const defer = (fnname) => {
- sampleRUM[fnname] = sampleRUM[fnname]
- || ((...args) => sampleRUM.defer.push({ fnname, args }));
+ sampleRUM[fnname] = sampleRUM[fnname] || ((...args) => sampleRUM.defer.push({ fnname, args }));
};
sampleRUM.drain = sampleRUM.drain
|| ((dfnname, fn) => {
@@ -28,27 +33,72 @@ export function sampleRUM(checkpoint, data = {}) {
.filter(({ fnname }) => dfnname === fnname)
.forEach(({ fnname, args }) => sampleRUM[fnname](...args));
});
- sampleRUM.on = (chkpnt, fn) => { sampleRUM.cases[chkpnt] = fn; };
+ sampleRUM.always = sampleRUM.always || [];
+ sampleRUM.always.on = (chkpnt, fn) => {
+ sampleRUM.always[chkpnt] = fn;
+ };
+ sampleRUM.on = (chkpnt, fn) => {
+ sampleRUM.cases[chkpnt] = fn;
+ };
defer('observe');
defer('cwv');
try {
window.hlx = window.hlx || {};
if (!window.hlx.rum) {
const usp = new URLSearchParams(window.location.search);
- const weight = (usp.get('rum') === 'on') ? 1 : 100; // with parameter, weight is 1. Defaults to 100.
- // eslint-disable-next-line no-bitwise
- const hashCode = (s) => s.split('').reduce((a, b) => (((a << 5) - a) + b.charCodeAt(0)) | 0, 0);
- const id = `${hashCode(window.location.href)}-${new Date().getTime()}-${Math.random().toString(16).substr(2, 14)}`;
+ const weight = usp.get('rum') === 'on' ? 1 : 100; // with parameter, weight is 1. Defaults to 100.
+ const id = Array.from({ length: 75 }, (_, i) => String.fromCharCode(48 + i))
+ .filter((a) => /\d|[A-Z]/i.test(a))
+ .filter(() => Math.random() * 75 > 70)
+ .join('');
const random = Math.random();
- const isSelected = (random * weight < 1);
- // eslint-disable-next-line object-curly-newline
- window.hlx.rum = { weight, id, random, isSelected, sampleRUM };
+ const isSelected = random * weight < 1;
+ const firstReadTime = Date.now();
+ const urlSanitizers = {
+ full: () => window.location.href,
+ origin: () => window.location.origin,
+ path: () => window.location.href.replace(/\?.*$/, ''),
+ };
+ // eslint-disable-next-line object-curly-newline, max-len
+ window.hlx.rum = {
+ weight,
+ id,
+ random,
+ isSelected,
+ firstReadTime,
+ sampleRUM,
+ sanitizeURL: urlSanitizers[window.hlx.RUM_MASK_URL || 'path'],
+ };
}
- const { weight, id } = window.hlx.rum;
+ const { weight, id, firstReadTime } = window.hlx.rum;
if (window.hlx && window.hlx.rum && window.hlx.rum.isSelected) {
+ const knownProperties = [
+ 'weight',
+ 'id',
+ 'referer',
+ 'checkpoint',
+ 't',
+ 'source',
+ 'target',
+ 'cwv',
+ 'CLS',
+ 'FID',
+ 'LCP',
+ 'INP',
+ ];
const sendPing = (pdata = data) => {
// eslint-disable-next-line object-curly-newline, max-len, no-use-before-define
- const body = JSON.stringify({ weight, id, referer: window.location.href, generation: window.hlx.RUM_GENERATION, checkpoint, ...data });
+ const body = JSON.stringify(
+ {
+ weight,
+ id,
+ referer: window.hlx.rum.sanitizeURL(),
+ checkpoint,
+ t: Date.now() - firstReadTime,
+ ...data,
+ },
+ knownProperties,
+ );
const url = `https://rum.hlx.page/.rum/${weight}`;
// eslint-disable-next-line no-unused-expressions
navigator.sendBeacon(url, body);
@@ -66,7 +116,12 @@ export function sampleRUM(checkpoint, data = {}) {
},
};
sendPing(data);
- if (sampleRUM.cases[checkpoint]) { sampleRUM.cases[checkpoint](); }
+ if (sampleRUM.cases[checkpoint]) {
+ sampleRUM.cases[checkpoint]();
+ }
+ }
+ if (sampleRUM.always[checkpoint]) {
+ sampleRUM.always[checkpoint](data);
}
} catch (error) {
// something went wrong
@@ -74,132 +129,66 @@ export function sampleRUM(checkpoint, data = {}) {
}
/**
- * Loads a CSS file.
- * @param {string} href The path to the CSS file
+ * Setup block utils.
*/
-export function loadCSS(href, callback) {
- if (!document.querySelector(`head > link[href="${href}"]`)) {
- const link = document.createElement('link');
- link.setAttribute('rel', 'stylesheet');
- link.setAttribute('href', href);
- if (typeof callback === 'function') {
- link.onload = (e) => callback(e.type);
- link.onerror = (e) => callback(e.type);
+function setup() {
+ window.hlx = window.hlx || {};
+ window.hlx.RUM_MASK_URL = 'full';
+ window.hlx.codeBasePath = '';
+ window.hlx.lighthouse = new URLSearchParams(window.location.search).get('lighthouse') === 'on';
+
+ const scriptEl = document.querySelector('script[src$="/scripts/scripts.js"]');
+ if (scriptEl) {
+ try {
+ [window.hlx.codeBasePath] = new URL(scriptEl.src).pathname.split('/scripts/scripts.js');
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.log(error);
}
- document.head.appendChild(link);
- } else if (typeof callback === 'function') {
- callback('noop');
}
}
/**
- * Retrieves the content of metadata tags.
- * @param {string} name The metadata name (or property)
- * @returns {string} The metadata value(s)
+ * Auto initializiation.
*/
-export function getMetadata(name) {
- const attr = name && name.includes(':') ? 'property' : 'name';
- const meta = [...document.head.querySelectorAll(`meta[${attr}="${name}"]`)].map((m) => m.content).join(', ');
- return meta || '';
-}
-/**
- * Sanitizes a name for use as class name.
- * @param {string} name The unsanitized name
- * @returns {string} The class name
- */
-export function toClassName(name) {
- return typeof name === 'string'
- ? name.toLowerCase().replace(/[^0-9a-z]/gi, '-').replace(/-+/g, '-').replace(/^-|-$/g, '')
- : '';
-}
+function init() {
+ setup();
+ sampleRUM('top');
-/*
- * Sanitizes a name for use as a js property name.
- * @param {string} name The unsanitized name
- * @returns {string} The camelCased name
- */
-export function toCamelCase(name) {
- return toClassName(name).replace(/-([a-z])/g, (g) => g[1].toUpperCase());
-}
+ window.addEventListener('load', () => sampleRUM('load'));
-/**
- * Replace icons with inline SVG and prefix with codeBasePath.
- * @param {Element} element
- */
-export function decorateIcons(element = document) {
- element.querySelectorAll('span.icon').forEach(async (span) => {
- if (span.classList.length < 2 || !span.classList[1].startsWith('icon-')) {
- return;
- }
- const icon = span.classList[1].substring(5);
- // eslint-disable-next-line no-use-before-define
- const resp = await fetch(`${(!window.__STORYBOOK_PREVIEW__) ? window.hlx.codeBasePath : ''}/icons/${icon}.svg`); // eslint-disable-line no-underscore-dangle
- if (resp.ok) {
- const iconHTML = await resp.text();
- if (iconHTML.match(/