File: /home/h340499/public_html/wp-content/plugins/wp-rocket/assets/js/wpr-beacon.js
(() => {
// src/Utils.js
var BeaconUtils = class {
static getScreenWidth() {
return window.innerWidth || document.documentElement.clientWidth;
}
static getScreenHeight() {
return window.innerHeight || document.documentElement.clientHeight;
}
static isNotValidScreensize(is_mobile, threshold) {
const screenWidth = this.getScreenWidth();
const screenHeight = this.getScreenHeight();
const isNotValidForMobile = is_mobile && (screenWidth > threshold.width || screenHeight > threshold.height);
const isNotValidForDesktop = !is_mobile && (screenWidth < threshold.width || screenHeight < threshold.height);
return isNotValidForMobile || isNotValidForDesktop;
}
static isPageCached() {
const signature = document.documentElement.nextSibling && document.documentElement.nextSibling.data ? document.documentElement.nextSibling.data : "";
return signature && signature.includes("Debug: cached");
}
static isIntersecting(rect) {
return rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth);
}
static isPageScrolled() {
return window.pageYOffset > 0 || document.documentElement.scrollTop > 0;
}
/**
* Checks if an element is visible in the viewport.
*
* This method checks if the provided element is visible in the viewport by
* considering its display, visibility, opacity, width, and height properties.
* It also excludes elements with transparent text properties.
* It returns true if the element is visible, and false otherwise.
*
* @param {Element} element - The element to check for visibility.
* @returns {boolean} True if the element is visible, false otherwise.
*/
static isElementVisible(element) {
const style = window.getComputedStyle(element);
const rect = element.getBoundingClientRect();
if (!style) {
return false;
}
if (this.hasTransparentText(element)) {
return false;
}
return !(style.display === "none" || style.visibility === "hidden" || style.opacity === "0" || rect.width === 0 || rect.height === 0);
}
/**
* Checks if an element has transparent text properties.
*
* This method checks for specific CSS properties that make text invisible,
* such as `color: transparent`, `color: rgba(..., 0)`, `color: hsla(..., 0)`,
* `color: #...00` (8-digit hex with alpha = 0), and `filter: opacity(0)`.
*
* @param {Element} element - The element to check.
* @returns {boolean} True if the element has transparent text properties, false otherwise.
*/
static hasTransparentText(element) {
const style = window.getComputedStyle(element);
if (!style) {
return false;
}
const color = style.color || "";
const filter = style.filter || "";
if (color === "transparent") {
return true;
}
const rgbaMatch = color.match(/rgba\(\d+,\s*\d+,\s*\d+,\s*0\)/);
if (rgbaMatch) {
return true;
}
const hslaMatch = color.match(/hsla\(\d+,\s*\d+%,\s*\d+%,\s*0\)/);
if (hslaMatch) {
return true;
}
const hexMatch = color.match(/#[0-9a-fA-F]{6}00/);
if (hexMatch) {
return true;
}
if (filter.includes("opacity(0)")) {
return true;
}
return false;
}
};
var Utils_default = BeaconUtils;
// src/BeaconLcp.js
var BeaconLcp = class {
constructor(config, logger) {
this.config = config;
this.performanceImages = [];
this.logger = logger;
}
async run() {
try {
const above_the_fold_images = this._generateLcpCandidates(Infinity);
if (above_the_fold_images) {
this._initWithFirstElementWithInfo(above_the_fold_images);
this._fillATFWithoutDuplications(above_the_fold_images);
}
} catch (err) {
this.errorCode = "script_error";
this.logger.logMessage("Script Error: " + err);
}
}
_generateLcpCandidates(count) {
const lcpElements = document.querySelectorAll(this.config.elements);
if (lcpElements.length <= 0) {
return [];
}
const potentialCandidates = Array.from(lcpElements);
const topCandidates = potentialCandidates.map((element) => {
if ("img" === element.nodeName.toLowerCase() && "picture" === element.parentElement.nodeName.toLowerCase()) {
return null;
}
let rect;
if ("picture" === element.nodeName.toLowerCase()) {
const imgElement = element.querySelector("img");
if (imgElement) {
rect = imgElement.getBoundingClientRect();
} else {
return null;
}
} else {
rect = element.getBoundingClientRect();
}
return {
element,
rect
};
}).filter((item) => item !== null).filter((item) => {
return item.rect.width > 0 && item.rect.height > 0 && Utils_default.isIntersecting(item.rect) && Utils_default.isElementVisible(item.element);
}).map((item) => ({
item,
area: this._getElementArea(item.rect),
elementInfo: this._getElementInfo(item.element)
})).sort((a, b) => b.area - a.area).slice(0, count);
return topCandidates.map((candidate) => ({
element: candidate.item.element,
elementInfo: candidate.elementInfo
}));
}
_getElementArea(rect) {
const visibleWidth = Math.min(rect.width, (window.innerWidth || document.documentElement.clientWidth) - rect.left);
const visibleHeight = Math.min(rect.height, (window.innerHeight || document.documentElement.clientHeight) - rect.top);
return visibleWidth * visibleHeight;
}
_getElementInfo(element) {
const nodeName = element.nodeName.toLowerCase();
const element_info = {
type: "",
src: "",
srcset: "",
sizes: "",
sources: [],
bg_set: [],
current_src: ""
};
const css_bg_url_rgx = /url\(\s*?['"]?\s*?(.+?)\s*?["']?\s*?\)/ig;
if (nodeName === "img" && element.srcset) {
element_info.type = "img-srcset";
element_info.src = element.src;
element_info.srcset = element.srcset;
element_info.sizes = element.sizes;
element_info.current_src = element.currentSrc;
} else if (nodeName === "img") {
element_info.type = "img";
element_info.src = element.src;
element_info.current_src = element.currentSrc;
} else if (nodeName === "video") {
element_info.type = "img";
const source = element.querySelector("source");
element_info.src = element.poster || (source ? source.src : "");
element_info.current_src = element_info.src;
} else if (nodeName === "svg") {
const imageElement = element.querySelector("image");
if (imageElement) {
element_info.type = "img";
element_info.src = imageElement.getAttribute("href") || "";
element_info.current_src = element_info.src;
}
} else if (nodeName === "picture") {
element_info.type = "picture";
const img = element.querySelector("img");
element_info.src = img ? img.src : "";
element_info.sources = Array.from(element.querySelectorAll("source")).map((source) => ({
srcset: source.srcset || "",
media: source.media || "",
type: source.type || "",
sizes: source.sizes || ""
}));
} else {
const computed_style = window.getComputedStyle(element, null);
const bg_props = [
computed_style.getPropertyValue("background-image"),
getComputedStyle(element, ":after").getPropertyValue("background-image"),
getComputedStyle(element, ":before").getPropertyValue("background-image")
].filter((prop) => prop !== "none");
if (bg_props.length === 0) {
return null;
}
const full_bg_prop = bg_props[0];
element_info.type = "bg-img";
if (full_bg_prop.includes("image-set(")) {
element_info.type = "bg-img-set";
}
if (!full_bg_prop || full_bg_prop === "" || full_bg_prop.includes("data:image")) {
return null;
}
const matches = [...full_bg_prop.matchAll(css_bg_url_rgx)];
element_info.bg_set = matches.map((m) => m[1] ? { src: m[1].trim() + (m[2] ? " " + m[2].trim() : "") } : {});
if (element_info.bg_set.every((item) => item.src === "")) {
element_info.bg_set = matches.map((m) => m[1] ? { src: m[1].trim() } : {});
}
if (element_info.bg_set.length <= 0) {
return null;
}
if (element_info.bg_set.length > 0) {
element_info.src = element_info.bg_set[0].src;
if (element_info.type === "bg-img-set") {
element_info.src = element_info.bg_set;
}
}
}
return element_info;
}
_initWithFirstElementWithInfo(elements) {
const firstElementWithInfo = elements.find((item) => {
return item.elementInfo !== null && (item.elementInfo.src || item.elementInfo.srcset);
});
if (!firstElementWithInfo) {
this.logger.logMessage("No LCP candidate found.");
this.performanceImages = [];
return;
}
this.performanceImages = [{
...firstElementWithInfo.elementInfo,
label: "lcp"
}];
}
_fillATFWithoutDuplications(elements) {
elements.forEach(({ element, elementInfo }) => {
if (this._isDuplicateImage(element) || !elementInfo) {
return;
}
this.performanceImages.push({ ...elementInfo, label: "above-the-fold" });
});
}
_isDuplicateImage(image) {
const elementInfo = this._getElementInfo(image);
if (elementInfo === null) {
return false;
}
const isImageOrVideo = elementInfo.type === "img" || elementInfo.type === "img-srcset" || elementInfo.type === "video";
const isBgImageOrPicture = elementInfo.type === "bg-img" || elementInfo.type === "bg-img-set" || elementInfo.type === "picture";
return (isImageOrVideo || isBgImageOrPicture) && this.performanceImages.some((item) => item.src === elementInfo.src);
}
getResults() {
return this.performanceImages;
}
};
var BeaconLcp_default = BeaconLcp;
// src/BeaconLrc.js
var BeaconLrc = class {
constructor(config, logger) {
this.config = config;
this.logger = logger;
this.lazyRenderElements = [];
}
async run() {
try {
const elementsInView = this._getLazyRenderElements();
if (elementsInView) {
this._processElements(elementsInView);
}
} catch (err) {
this.errorCode = "script_error";
this.logger.logMessage("Script Error: " + err);
}
}
_getLazyRenderElements() {
const elements = document.querySelectorAll("[data-rocket-location-hash]");
const svgUseTargets = this._getSvgUseTargets();
if (elements.length <= 0) {
return [];
}
const validElements = Array.from(elements).filter((element) => {
if (this._skipElement(element)) {
return false;
}
if (svgUseTargets.includes(element)) {
this.logger.logColoredMessage(`Element skipped because of SVG: ${element.tagName}`, "orange");
return false;
}
return true;
});
return validElements.map((element) => ({
element,
depth: this._getElementDepth(element),
distance: this._getElementDistance(element),
hash: this._getLocationHash(element)
}));
}
_getElementDepth(element) {
let depth = 0;
let parent = element.parentElement;
while (parent) {
depth++;
parent = parent.parentElement;
}
return depth;
}
_getElementDistance(element) {
const rect = element.getBoundingClientRect();
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
return Math.max(0, rect.top + scrollTop - Utils_default.getScreenHeight());
}
_skipElement(element) {
const skipStrings = this.config.skipStrings || ["memex"];
if (!element || !element.id) return false;
return skipStrings.some((str) => element.id.toLowerCase().includes(str.toLowerCase()));
}
_shouldSkipElement(element, exclusions) {
if (!element) return false;
for (let i = 0; i < exclusions.length; i++) {
const [attribute, pattern] = exclusions[i];
const attributeValue = element.getAttribute(attribute);
if (attributeValue && new RegExp(pattern, "i").test(attributeValue)) {
return true;
}
}
return false;
}
_checkLcrConflict(element) {
const conflictingElements = [];
const computedStyle = window.getComputedStyle(element);
const validMargins = ["marginTop", "marginRight", "marginBottom", "marginLeft"];
const negativeMargins = validMargins.some((margin) => parseFloat(computedStyle[margin]) < 0);
const currentElementConflicts = negativeMargins || computedStyle.contentVisibility === "auto" || computedStyle.contentVisibility === "hidden";
if (currentElementConflicts) {
conflictingElements.push({
element,
conflicts: [
negativeMargins && "negative margin",
computedStyle.contentVisibility === "auto" && "content-visibility:auto",
computedStyle.contentVisibility === "hidden" && "content-visibility:hidden"
].filter(Boolean)
});
}
Array.from(element.children).forEach((child) => {
const childStyle = window.getComputedStyle(child);
const validMargins2 = ["marginTop", "marginRight", "marginBottom", "marginLeft"];
const childNegativeMargins = validMargins2.some((margin) => parseFloat(childStyle[margin]) < 0);
const childConflicts = childNegativeMargins || childStyle.position === "absolute" || childStyle.position === "fixed";
if (childConflicts) {
conflictingElements.push({
element: child,
conflicts: [
childNegativeMargins && "negative margin",
childStyle.position === "absolute" && "position:absolute",
childStyle.position === "fixed" && "position:fixed"
].filter(Boolean)
});
}
});
return conflictingElements;
}
_processElements(elements) {
elements.forEach(({ element, depth, distance, hash }) => {
if (this._shouldSkipElement(element, this.config.exclusions || [])) {
return;
}
if ("No hash detected" === hash) {
return;
}
const conflicts = this._checkLcrConflict(element);
if (conflicts.length > 0) {
this.logger.logMessage("Skipping element due to conflicts:", conflicts);
return;
}
const can_push_hash = element.parentElement && this._getElementDistance(element.parentElement) < this.config.lrc_threshold && distance >= this.config.lrc_threshold;
const color = can_push_hash ? "green" : distance === 0 ? "red" : "";
this.logger.logColoredMessage(`${" ".repeat(depth)}${element.tagName} (Depth: ${depth}, Distance from viewport bottom: ${distance}px)`, color);
this.logger.logColoredMessage(`${" ".repeat(depth)}Location hash: ${hash}`, color);
this.logger.logColoredMessage(`${" ".repeat(depth)}Dimensions Client Height: ${element.clientHeight}`, color);
if (can_push_hash) {
this.lazyRenderElements.push(hash);
this.logger.logMessage(`Element pushed with hash: ${hash}`);
}
});
}
_getXPath(element) {
if (element && element.id !== "") {
return `//*[@id="${element.id}"]`;
}
return this._getElementXPath(element);
}
_getElementXPath(element) {
if (element === document.body) {
return "/html/body";
}
const position = this._getElementPosition(element);
return `${this._getElementXPath(element.parentNode)}/${element.nodeName.toLowerCase()}[${position}]`;
}
_getElementPosition(element) {
let pos = 1;
let sibling = element.previousElementSibling;
while (sibling) {
if (sibling.nodeName === element.nodeName) {
pos++;
}
sibling = sibling.previousElementSibling;
}
return pos;
}
_getLocationHash(element) {
return element.hasAttribute("data-rocket-location-hash") ? element.getAttribute("data-rocket-location-hash") : "No hash detected";
}
_getSvgUseTargets() {
const useElements = document.querySelectorAll("use");
const targets = /* @__PURE__ */ new Set();
useElements.forEach((use) => {
let parent = use.parentElement;
while (parent && parent !== document.body) {
targets.add(parent);
parent = parent.parentElement;
}
});
return Array.from(targets);
}
getResults() {
return this.lazyRenderElements;
}
};
var BeaconLrc_default = BeaconLrc;
// src/BeaconPreloadFonts.js
var BeaconPreloadFonts = class {
constructor(config, logger) {
this.config = config;
this.logger = logger;
this.aboveTheFoldFonts = [];
const extensions = (Array.isArray(this.config.processed_extensions) && this.config.processed_extensions.length > 0 ? this.config.processed_extensions : ["woff", "woff2", "ttf"]).map((ext) => ext.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
this.FONT_FILE_REGEX = new RegExp(`\\.(${extensions})(\\?.*)?$`, "i");
this.EXCLUDED_TAG_NAMES = /* @__PURE__ */ new Set([
// Metadata/document head
"BASE",
"HEAD",
"LINK",
"META",
"STYLE",
"TITLE",
"SCRIPT",
// Media
"IMG",
"VIDEO",
"AUDIO",
"EMBED",
"OBJECT",
"IFRAME",
// Templating, wrappers, components, fallback
"NOSCRIPT",
"TEMPLATE",
"SLOT",
"CANVAS",
// Resources
"SOURCE",
"TRACK",
"PARAM",
// SVG references
"USE",
"SYMBOL",
// Layout work
"BR",
"HR",
"WBR",
// Obsolete/deprecated
"APPLET",
"ACRONYM",
"BGSOUND",
"BIG",
"BLINK",
"CENTER",
"FONT",
"FRAME",
"FRAMESET",
"MARQUEE",
"NOFRAMES",
"STRIKE",
"TT",
"U",
"XMP"
]);
}
/**
* Checks if a URL should be excluded from external font processing based on domain exclusions.
*
* @param {string} url - The URL to check.
* @returns {boolean} True if the URL should be excluded, false otherwise.
*/
isUrlExcludedFromExternalProcessing(url) {
if (!url) return false;
const externalFontExclusions = this.config.external_font_exclusions || [];
const preloadFontsExclusions = this.config.preload_fonts_exclusions || [];
const allExclusions = [...externalFontExclusions, ...preloadFontsExclusions];
return allExclusions.some((exclusion) => url.includes(exclusion));
}
/**
* Checks if a font family or URL should be excluded from preloading.
*
* This method determines if the provided font family or any of its URLs
* match any exclusion patterns defined in the configuration. It checks for
* exact matches and substring matches for both the font family and URLs.
*
* @param {string} fontFamily - The font family to check.
* @param {string[]} urls - Array of font file URLs to check.
* @returns {boolean} True if the font should be excluded, false otherwise.
*/
isExcluded(fontFamily, urls) {
const exclusions = this.config.preload_fonts_exclusions;
const exclusionsSet = new Set(exclusions);
if (exclusionsSet.has(fontFamily)) {
return true;
}
if (exclusions.some((exclusion) => fontFamily.includes(exclusion))) {
return true;
}
if (Array.isArray(urls) && urls.length > 0) {
if (urls.some((url) => exclusionsSet.has(url))) {
return true;
}
if (urls.some(
(url) => exclusions.some((exclusion) => url.includes(exclusion))
)) {
return true;
}
}
return false;
}
/**
* Checks if an element can be styled with font-family.
*
* This method determines if the provided element's tag name is not in the list
* of excluded tag names that cannot be styled with font-family CSS property.
*
* @param {Element} element - The element to check.
* @returns {boolean} True if the element can be styled with font-family, false otherwise.
*/
canElementBeStyledWithFontFamily(element) {
return !this.EXCLUDED_TAG_NAMES.has(element.tagName);
}
/**
* Checks if an element is visible in the viewport.
*
* This method delegates to BeaconUtils.isElementVisible() for consistent
* visibility checking across all beacons.
*
* @param {Element} element - The element to check for visibility.
* @returns {boolean} True if the element is visible, false otherwise.
*/
isElementVisible(element) {
return Utils_default.isElementVisible(element);
}
/**
* Cleans a URL by removing query parameters and fragments.
*
* This method takes a URL as input, removes any query parameters and fragments,
* and returns the cleaned URL.
*
* @param {string} url - The URL to clean.
* @returns {string} The cleaned URL.
*/
cleanUrl(url) {
try {
url = url.split("?")[0].split("#")[0];
return new URL(url, window.location.href).href;
} catch (e) {
return url;
}
}
/**
* Fetches external stylesheet links from known font providers, retrieves their CSS,
* parses them into in-memory CSSStyleSheet objects, and extracts font-family/font-face
* information into a structured object.
*
* @async
* @function externalStylesheetsDoc
* @returns {Promise<{styleSheets: CSSStyleSheet[], fontPairs: Object}>} An object containing:
* - styleSheets: Array of parsed CSSStyleSheet objects (not attached to the DOM).
* - fontPairs: An object mapping font URLs to arrays of font variation objects
* ({family, weight, style}).
*
* @example
* const { styleSheets, fontPairs } = await externalStylesheetsDoc();
* this.logger.logMessage(fontPairs);
*/
async externalStylesheetsDoc() {
function generateFontPairsFromStyleSheets(styleSheetsArray) {
const fontPairs = {};
function _extractFirstUrlFromSrc(srcValue) {
if (!srcValue) return null;
const urlMatch = srcValue.match(/url\s*\(\s*(['"]?)(.+?)\1\s*\)/);
return urlMatch ? urlMatch[2] : null;
}
function _cleanFontFamilyName(fontFamilyValue) {
if (!fontFamilyValue) return "";
return fontFamilyValue.replace(/^['"]+|['"]+$/g, "").trim();
}
if (!styleSheetsArray || !Array.isArray(styleSheetsArray)) {
console.warn(
"generateFontPairsFromStyleSheets: Input is not a valid array. Received:",
styleSheetsArray
);
return fontPairs;
}
if (styleSheetsArray.length === 0) {
return fontPairs;
}
styleSheetsArray.forEach((sheet) => {
if (sheet && sheet.cssRules) {
try {
for (const rule of sheet.cssRules) {
if (rule.type === CSSRule.FONT_FACE_RULE) {
const cssFontFaceRule = rule;
const fontFamily = _cleanFontFamilyName(
cssFontFaceRule.style.getPropertyValue("font-family")
);
const fontWeight = cssFontFaceRule.style.getPropertyValue("font-weight") || "normal";
const fontStyle = cssFontFaceRule.style.getPropertyValue("font-style") || "normal";
const src = cssFontFaceRule.style.getPropertyValue("src");
const fontUrl = _extractFirstUrlFromSrc(src);
if (fontFamily && fontUrl) {
const variation = {
family: fontFamily,
weight: fontWeight,
style: fontStyle
};
if (!fontPairs[fontUrl]) fontPairs[fontUrl] = [];
const variationExists = fontPairs[fontUrl].some(
(v) => v.family === variation.family && v.weight === variation.weight && v.style === variation.style
);
if (!variationExists) fontPairs[fontUrl].push(variation);
}
}
}
} catch (e) {
console.warn(
"Error processing CSS rules from a stylesheet:",
e,
sheet
);
}
} else if (sheet && !sheet.cssRules) {
console.warn(
"Skipping a stylesheet as its cssRules are not accessible or it is empty:",
sheet
);
}
});
return fontPairs;
}
const links = [
...document.querySelectorAll('link[rel="stylesheet"]')
].filter((link) => {
try {
const linkUrl = new URL(link.href);
const currentUrl = new URL(window.location.href);
if (linkUrl.origin === currentUrl.origin) {
return false;
}
return !this.isUrlExcludedFromExternalProcessing(link.href);
} catch (e) {
return false;
}
});
if (links.length === 0) {
this.logger.logMessage("No external CSS links found to process.");
return {
// Consistent return structure
styleSheets: [],
// The retrievable CSSStyleSheet objects
fontPairs: {}
// Processed data from these sheets
};
}
const fetchedCssPromises = links.map(
(linkElement) => fetch(linkElement.href, { mode: "cors" }).then((response) => {
if (response.ok) {
return response.text();
}
console.warn(
`Failed to fetch external CSS from ${linkElement.href}: ${response.status} ${response.statusText}`
);
return null;
}).catch((error) => {
console.error(
`Network error fetching external CSS from ${linkElement.href}:`,
error
);
return null;
})
);
const cssTexts = await Promise.all(fetchedCssPromises);
const temporaryStyleSheets = [];
cssTexts.forEach((txt) => {
if (txt && txt.trim() !== "") {
try {
const sheet = new CSSStyleSheet();
sheet.replaceSync(txt);
temporaryStyleSheets.push(sheet);
} catch (error) {
console.error(
"Could not parse fetched CSS into a stylesheet:",
error,
`
CSS (first 200 chars): ${txt.substring(0, 200)}...`
);
}
}
});
if (temporaryStyleSheets.length > 0) {
this.logger.logMessage(
`[Beacon] ${temporaryStyleSheets.length} stylesheet(s) fetched and parsed into CSSStyleSheet objects.`
);
} else {
this.logger.logMessage(
"[Beacon] No stylesheets were successfully parsed from the fetched CSS."
);
}
const processedFontPairs = generateFontPairsFromStyleSheets(temporaryStyleSheets);
return {
styleSheets: temporaryStyleSheets,
fontPairs: processedFontPairs
};
}
/**
* Asynchronously initializes and parses external font stylesheets.
*
* Fetches external font stylesheets and font pairs using `externalStylesheetsDoc`,
* then stores the parsed results in `externalParsedSheets` and `externalParsedPairs`.
* Logs the process and handles errors by resetting `externalParsedSheets` to an empty array.
*
* @async
* @returns {Promise<void>} Resolves when external font stylesheets have been initialized.
*/
async _initializeExternalFontSheets() {
this.logger.logMessage("Initializing external font stylesheets...");
try {
const result = await this.externalStylesheetsDoc();
this.externalParsedSheets = result.styleSheets || [];
this.externalParsedPairs = result.fontPairs || [];
this.logger.logMessage(
`Successfully parsed ${this.externalParsedSheets.length} external font stylesheets.`
);
} catch (error) {
this.logger.logMessage(
"Error initializing external font stylesheets:",
error
);
this.externalParsedSheets = [];
}
}
/**
* Retrieves a map of network-loaded fonts.
*
* This method uses the Performance API to get all resource entries, filters out
* the ones that match the font file regex, and maps them to their cleaned URLs.
*
* @returns {Map} A map where each key is a cleaned URL of a font file and
* each value is the original URL of the font file.
*/
getNetworkLoadedFonts() {
return new Map(
window.performance.getEntriesByType("resource").filter((resource) => this.FONT_FILE_REGEX.test(resource.name)).map((resource) => [this.cleanUrl(resource.name), resource.name])
);
}
/**
* Retrieves font-face rules from stylesheets.
*
* This method scans all stylesheets loaded on the page and collects
* font-face rules, including their source URLs, font families, weights,
* and styles. It returns an object containing the collected font data.
*
* @returns {Promise<Object>} An object mapping font families to their respective
* URLs and variations.
*/
async getFontFaceRules() {
const stylesheetFonts = {};
const processedUrls = /* @__PURE__ */ new Set();
const processFontFaceRule = (rule, baseHref = null) => {
const src = rule.style.getPropertyValue("src");
const fontFamily = rule.style.getPropertyValue("font-family").replace(/['"]/g, "").trim();
const weight = rule.style.getPropertyValue("font-weight") || "400";
const style = rule.style.getPropertyValue("font-style") || "normal";
if (!stylesheetFonts[fontFamily]) {
stylesheetFonts[fontFamily] = { urls: [], variations: /* @__PURE__ */ new Set() };
}
const extractFirstUrlFromSrc = (srcValue) => {
if (!srcValue) return null;
const urlMatch = srcValue.match(/url\s*\(\s*(['"]?)(.+?)\1\s*\)/);
return urlMatch ? urlMatch[2] : null;
};
const firstUrl = extractFirstUrlFromSrc(src);
if (firstUrl) {
let rawUrl = firstUrl;
if (baseHref) {
rawUrl = new URL(rawUrl, baseHref).href;
}
const normalized = this.cleanUrl(rawUrl);
if (!stylesheetFonts[fontFamily].urls.includes(normalized)) {
stylesheetFonts[fontFamily].urls.push(normalized);
stylesheetFonts[fontFamily].variations.add(
JSON.stringify({ weight, style })
);
}
}
};
const processImportRule = async (rule) => {
try {
const importUrl = rule.href;
if (this.isUrlExcludedFromExternalProcessing(importUrl)) {
return;
}
if (processedUrls.has(importUrl)) {
return;
}
processedUrls.add(importUrl);
const response = await fetch(importUrl, { mode: "cors" });
if (!response.ok) {
this.logger.logMessage(`Failed to fetch @import CSS: ${response.status}`);
return;
}
const cssText = await response.text();
const tempSheet = new CSSStyleSheet();
tempSheet.replaceSync(cssText);
Array.from(tempSheet.cssRules || []).forEach((importedRule) => {
if (importedRule instanceof CSSFontFaceRule) {
processFontFaceRule(importedRule, importUrl);
}
});
} catch (error) {
this.logger.logMessage(`Error processing @import rule: ${error.message}`);
}
};
const processSheet = async (sheet) => {
try {
const rules = Array.from(sheet.cssRules || []);
for (const rule of rules) {
if (rule instanceof CSSFontFaceRule) {
processFontFaceRule(rule, sheet.href);
} else if (rule instanceof CSSImportRule) {
if (rule.styleSheet) {
await processSheet(rule.styleSheet);
} else {
await processImportRule(rule);
}
} else if (rule.styleSheet) {
await processSheet(rule.styleSheet);
}
}
} catch (e) {
if (e.name === "SecurityError" && sheet.href) {
if (this.isUrlExcludedFromExternalProcessing(sheet.href)) {
return;
}
if (processedUrls.has(sheet.href)) {
return;
}
processedUrls.add(sheet.href);
try {
const response = await fetch(sheet.href, { mode: "cors" });
if (response.ok) {
const cssText = await response.text();
const tempSheet = new CSSStyleSheet();
tempSheet.replaceSync(cssText);
Array.from(tempSheet.cssRules || []).forEach((rule) => {
if (rule instanceof CSSFontFaceRule) {
processFontFaceRule(rule, sheet.href);
}
});
const importRegex = /@import\s+url\(['"]?([^'")]+)['"]?\);?/g;
let importMatch;
while ((importMatch = importRegex.exec(cssText)) !== null) {
const importUrl = new URL(importMatch[1], sheet.href).href;
if (this.isUrlExcludedFromExternalProcessing(importUrl)) {
continue;
}
if (processedUrls.has(importUrl)) {
continue;
}
processedUrls.add(importUrl);
try {
const importResponse = await fetch(importUrl, { mode: "cors" });
if (importResponse.ok) {
const importCssText = await importResponse.text();
const tempImportSheet = new CSSStyleSheet();
tempImportSheet.replaceSync(importCssText);
Array.from(tempImportSheet.cssRules || []).forEach((importedRule) => {
if (importedRule instanceof CSSFontFaceRule) {
processFontFaceRule(importedRule, importUrl);
}
});
}
} catch (importError) {
this.logger.logMessage(`Error fetching @import ${importUrl}: ${importError.message}`);
}
}
}
} catch (fetchError) {
this.logger.logMessage(`Error fetching stylesheet ${sheet.href}: ${fetchError.message}`);
}
} else {
this.logger.logMessage(`Error processing stylesheet: ${e.message}`);
}
}
};
const sheets = Array.from(document.styleSheets);
for (const sheet of sheets) {
await processSheet(sheet);
}
const inlineStyleElements = document.querySelectorAll("style");
for (const styleElement of inlineStyleElements) {
const cssText = styleElement.textContent || styleElement.innerHTML || "";
const importRegex = /@import\s+url\s*\(\s*['"]?([^'")]+)['"]?\s*\)\s*;?/g;
let importMatch;
while ((importMatch = importRegex.exec(cssText)) !== null) {
const importUrl = importMatch[1];
if (this.isUrlExcludedFromExternalProcessing(importUrl)) {
continue;
}
if (processedUrls.has(importUrl)) {
continue;
}
processedUrls.add(importUrl);
try {
const response = await fetch(importUrl, { mode: "cors" });
if (response.ok) {
const importCssText = await response.text();
const tempSheet = new CSSStyleSheet();
tempSheet.replaceSync(importCssText);
Array.from(tempSheet.cssRules || []).forEach((importedRule) => {
if (importedRule instanceof CSSFontFaceRule) {
processFontFaceRule(importedRule, importUrl);
}
});
}
} catch (importError) {
this.logger.logMessage(`Error fetching inline @import ${importUrl}: ${importError.message}`);
}
}
}
Object.values(stylesheetFonts).forEach((fontData) => {
fontData.variations = Array.from(fontData.variations).map((v) => JSON.parse(v));
});
return stylesheetFonts;
}
/**
* Checks if an element is above the fold (visible in the viewport without scrolling).
*
* @param {Element} element - The element to check.
* @returns {boolean} True if the element is above the fold, false otherwise.
*/
isElementAboveFold(element) {
if (!this.isElementVisible(element)) return false;
const rect = element.getBoundingClientRect();
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const elementTop = rect.top + scrollTop;
const foldPosition = window.innerHeight || document.documentElement.clientHeight;
return elementTop <= foldPosition;
}
/**
* Checks if an element can be processed for font analysis.
*
* This method combines checks for whether an element can be styled with font-family
* and whether it is above the fold, providing a single method to determine if an
* element should be processed during font analysis.
*
* @param {Element} element - The element to check.
* @returns {boolean} True if the element can be processed, false otherwise.
*/
canElementBeProcessed(element) {
return this.canElementBeStyledWithFontFamily(element) && this.isElementAboveFold(element);
}
/**
* Initiates the process of analyzing and summarizing font usage on the page.
* This method fetches network-loaded fonts, stylesheet fonts, and external font pairs.
* It then processes each element on the page to determine which fonts are used above the fold.
* The results are summarized and logged.
*
* @returns {Promise<void>} A promise that resolves when the analysis is complete.
*/
async run() {
await document.fonts.ready;
await this._initializeExternalFontSheets();
const networkLoadedFonts = this.getNetworkLoadedFonts();
const stylesheetFonts = await this.getFontFaceRules();
const hostedFonts = /* @__PURE__ */ new Map();
const externalFontsResults = await this.processExternalFonts(this.externalParsedPairs);
const elements = Array.from(document.getElementsByTagName("*")).filter((el) => this.canElementBeProcessed(el));
elements.forEach((element) => {
const processElementFont = (style, pseudoElement = null) => {
if (!style || !this.isElementVisible(element)) return;
const fontFamily = style.fontFamily.split(",")[0].replace(/['"]+/g, "").trim();
const hasContent = pseudoElement ? style.content !== "none" && style.content !== '""' : element.textContent.trim();
if (hasContent && stylesheetFonts[fontFamily]) {
let urls = stylesheetFonts[fontFamily].urls;
if (!this.isExcluded(fontFamily, urls) && !hostedFonts.has(fontFamily)) {
hostedFonts.set(fontFamily, {
elements: /* @__PURE__ */ new Set(),
urls,
variations: stylesheetFonts[fontFamily].variations
});
hostedFonts.get(fontFamily).elements.add(element);
}
}
};
try {
processElementFont(window.getComputedStyle(element));
["::before", "::after"].forEach((pseudo) => {
processElementFont(window.getComputedStyle(element, pseudo), pseudo);
});
} catch (e) {
this.logger.logMessage("Error processing element:", e);
}
});
const aboveTheFoldFonts = this.summarizeMatches(externalFontsResults, hostedFonts, networkLoadedFonts);
if (!Object.keys(aboveTheFoldFonts.allFonts).length && !Object.keys(aboveTheFoldFonts.externalFonts).length && !Object.keys(aboveTheFoldFonts.hostedFonts).length) {
this.logger.logMessage("No fonts found above the fold.");
return;
}
this.logger.logMessage("Above the fold fonts:", aboveTheFoldFonts);
this.aboveTheFoldFonts = [...new Set(Object.values(aboveTheFoldFonts.allFonts).flatMap((font) => font.variations.map((variation) => variation.url)))];
}
/**
* Summarizes all font matches found on the page
* Creates a comprehensive object containing font usage data
*
* @param {Object} externalFontsResults - Results from External Fonts analysis
* @param {Map} hostedFonts - Map of hosted (non-External) fonts found
* @param {Map} networkLoadedFonts - Map of all font files loaded via network
* @returns {Object} Complete analysis of font usage including locations and counts
*/
summarizeMatches(externalFontsResults, hostedFonts, networkLoadedFonts) {
const allFonts = {};
const hostedFontsResults = {};
if (hostedFonts.size > 0) {
hostedFonts.forEach((data, fontFamily) => {
if (data.variations) {
const elements = Array.from(data.elements);
const aboveElements = elements.filter((el) => this.isElementAboveFold(el));
const belowElements = elements.filter((el) => !this.isElementAboveFold(el));
data.variations.forEach((variation) => {
let matchingUrl = null;
for (const styleUrl of data.urls) {
const normalizedStyleUrl = this.cleanUrl(styleUrl);
if (networkLoadedFonts.has(normalizedStyleUrl)) {
matchingUrl = networkLoadedFonts.get(normalizedStyleUrl);
break;
}
}
if (matchingUrl) {
if (!allFonts[fontFamily]) {
allFonts[fontFamily] = {
type: "hosted",
variations: [],
elementCount: {
aboveFold: aboveElements.length,
belowFold: belowElements.length,
total: elements.length
},
urlCount: {
aboveFold: /* @__PURE__ */ new Set(),
belowFold: /* @__PURE__ */ new Set()
}
};
}
allFonts[fontFamily].variations.push({
weight: variation.weight,
style: variation.style,
url: matchingUrl,
elementCount: {
aboveFold: aboveElements.length,
belowFold: belowElements.length,
total: elements.length
}
});
if (aboveElements.length > 0) {
allFonts[fontFamily].urlCount.aboveFold.add(matchingUrl);
}
if (belowElements.length > 0) {
allFonts[fontFamily].urlCount.belowFold.add(matchingUrl);
}
}
});
if (allFonts[fontFamily]) {
hostedFontsResults[fontFamily] = {
variations: allFonts[fontFamily].variations,
elementCount: { ...allFonts[fontFamily].elementCount },
urlCount: { ...allFonts[fontFamily].urlCount }
};
}
}
});
}
if (Object.keys(externalFontsResults).length > 0) {
Object.entries(externalFontsResults).forEach(([url, data]) => {
const aboveElements = Array.from(data.elements).filter((el) => this.isElementAboveFold(el));
const belowElements = Array.from(data.elements).filter((el) => !this.isElementAboveFold(el));
if (data.elementCount.aboveFold > 0 || aboveElements.length > 0) {
data.variations.forEach((variation) => {
if (!allFonts[variation.family]) {
allFonts[variation.family] = {
type: "external",
variations: [],
// Track element counts at font family level
elementCount: {
aboveFold: 0,
belowFold: 0,
total: 0
},
// Track unique URLs used in each fold location
urlCount: {
aboveFold: /* @__PURE__ */ new Set(),
belowFold: /* @__PURE__ */ new Set()
}
};
}
allFonts[variation.family].variations.push({
weight: variation.weight,
style: variation.style,
url,
elementCount: {
aboveFold: aboveElements.length,
belowFold: belowElements.length,
total: data.elements.length
}
});
allFonts[variation.family].elementCount.aboveFold += aboveElements.length;
allFonts[variation.family].elementCount.belowFold += belowElements.length;
allFonts[variation.family].elementCount.total += data.elements.length;
if (aboveElements.length > 0) {
allFonts[variation.family].urlCount.aboveFold.add(url);
}
if (belowElements.length > 0) {
allFonts[variation.family].urlCount.belowFold.add(url);
}
});
}
});
}
Object.values(allFonts).forEach((font) => {
font.urlCount = {
aboveFold: font.urlCount.aboveFold.size,
belowFold: font.urlCount.belowFold.size,
total: (/* @__PURE__ */ new Set([...font.urlCount.aboveFold, ...font.urlCount.belowFold])).size
};
});
Object.values(hostedFontsResults).forEach((font) => {
if (font.urlCount.aboveFold instanceof Set) {
font.urlCount = {
aboveFold: font.urlCount.aboveFold.size,
belowFold: font.urlCount.belowFold.size,
total: (/* @__PURE__ */ new Set([...font.urlCount.aboveFold, ...font.urlCount.belowFold])).size
};
}
});
return {
externalFonts: Object.fromEntries(
Object.entries(externalFontsResults).filter(
(entry) => entry[1].elementCount.aboveFold > 0
)
),
hostedFonts: hostedFontsResults,
allFonts
};
}
/**
* Processes external font pairs to identify their usage on the page.
*
* This method iterates through all elements on the page, checks if they are above the fold,
* and determines the font information for each element. It then matches the font information
* with the provided external font pairs to identify which fonts are used and where.
*
* @param {Object} fontPairs - An object where each key is a URL and the value is an array of font variations.
* @returns {Promise<Object>} A promise that resolves to an object where each key is a URL and the value is an object containing information about the elements using that font.
*/
async processExternalFonts(fontPairs) {
const matches = /* @__PURE__ */ new Map();
const elements = Array.from(document.getElementsByTagName("*")).filter((el) => this.canElementBeProcessed(el));
const fontMap = /* @__PURE__ */ new Map();
Object.entries(fontPairs).forEach(([url, variations]) => {
variations.forEach((variation) => {
const key = `${variation.family}|${variation.weight}|${variation.style}`;
fontMap.set(key, { url, ...variation });
});
});
const getFontInfoForElement = (style) => {
const family = style.fontFamily.split(",")[0].replace(/['"]+/g, "").trim();
const weight = style.fontWeight;
const fontStyle = style.fontStyle;
const key = `${family}|${weight}|${fontStyle}`;
let fontInfo = fontMap.get(key);
if (!fontInfo && weight !== "400") {
const fallbackKey = `${family}|400|${fontStyle}`;
fontInfo = fontMap.get(fallbackKey);
}
return fontInfo;
};
elements.forEach((element) => {
if (element.textContent.trim()) {
const style = window.getComputedStyle(element);
const fontInfo = getFontInfoForElement(style);
if (fontInfo) {
if (!this.isExcluded(fontInfo.family, [fontInfo.url]) && !matches.has(fontInfo.url)) {
matches.set(fontInfo.url, {
elements: /* @__PURE__ */ new Set(),
variations: /* @__PURE__ */ new Set()
});
matches.get(fontInfo.url).elements.add(element);
matches.get(fontInfo.url).variations.add(JSON.stringify({
family: fontInfo.family,
weight: fontInfo.weight,
style: fontInfo.style
}));
}
}
}
["::before", "::after"].forEach((pseudo) => {
const pseudoStyle = window.getComputedStyle(element, pseudo);
if (pseudoStyle.content !== "none" && pseudoStyle.content !== '""') {
const fontInfo = getFontInfoForElement(pseudoStyle);
if (fontInfo) {
if (!this.isExcluded(fontInfo.family, [fontInfo.url]) && !matches.has(fontInfo.url)) {
matches.set(fontInfo.url, {
elements: /* @__PURE__ */ new Set(),
variations: /* @__PURE__ */ new Set()
});
matches.get(fontInfo.url).elements.add(element);
matches.get(fontInfo.url).variations.add(JSON.stringify({
family: fontInfo.family,
weight: fontInfo.weight,
style: fontInfo.style
}));
}
}
}
});
});
return Object.fromEntries(
Array.from(matches.entries()).map(([url, data]) => [
url,
{
elementCount: {
aboveFold: Array.from(data.elements).filter((el) => this.isElementAboveFold(el)).length,
total: data.elements.size
},
variations: Array.from(data.variations).map((v) => JSON.parse(v)),
elements: Array.from(data.elements)
}
])
);
}
/**
* Retrieves the results of the font analysis, specifically the fonts used above the fold.
* This method returns an array containing the URLs of the fonts used above the fold.
*
* @returns {Array<string>} An array of URLs of the fonts used above the fold.
*/
getResults() {
return this.aboveTheFoldFonts;
}
};
var BeaconPreloadFonts_default = BeaconPreloadFonts;
// src/BeaconPreconnectExternalDomain.js
var BeaconPreconnectExternalDomain = class {
constructor(config, logger) {
this.logger = logger;
this.result = [];
this.excludedPatterns = config.preconnect_external_domain_exclusions;
this.eligibleElements = config.preconnect_external_domain_elements;
this.matchedItems = /* @__PURE__ */ new Set();
this.excludedItems = /* @__PURE__ */ new Set();
}
/**
* Initiates the process of identifying and logging external domains that require preconnection.
* This method queries the document for eligible elements, processes each element to determine
* if it should be preconnected, and logs the results.
*/
async run() {
const elements = document.querySelectorAll(
`${this.eligibleElements.join(", ")}[src], ${this.eligibleElements.join(", ")}[href], ${this.eligibleElements.join(", ")}[rel], ${this.eligibleElements.join(", ")}[type]`
);
elements.forEach((el) => this.processElement(el));
this.logger.logMessage({ matchedItems: this.getMatchedItems(), excludedItems: Array.from(this.excludedItems) });
}
/**
* Processes a single element to determine if it should be preconnected.
*
* This method checks if the element is excluded based on attribute or domain rules.
* If not excluded, it checks if the element's URL is an external domain and adds it to the list of matched items.
*
* @param {Element} el - The element to process.
*/
processElement(el) {
try {
const url = new URL(el.src || el.href || "", location.href);
if (this.isExcluded(el)) {
this.excludedItems.add(this.createExclusionObject(url, el));
return;
}
if (this.isExternalDomain(url)) {
this.matchedItems.add(`${url.hostname}-${el.tagName.toLowerCase()}`);
this.result = [...new Set(this.result.concat(url.origin))];
}
} catch (e) {
this.logger.logMessage(e);
}
}
/**
* Checks if an element is excluded based on exclusions patterns.
*
* This method iterates through the excludedPatterns array and checks if any pattern matches any of the element's attribute or values.
* If a match is found, it returns true, indicating the element is excluded.
*
* @param {Element} el - The element to check.
* @returns {boolean} True if the element is excluded by an attribute rule, false otherwise.
*/
isExcluded(el) {
const outerHTML = el.outerHTML.substring(0, el.outerHTML.indexOf(">") + 1);
return this.excludedPatterns.some(
(pattern) => outerHTML.includes(pattern)
);
}
/**
* Checks if a URL is excluded based on domain rules.
*
* This method iterates through the excludedPatterns array and checks if any pattern matches the URL's hostname.
* If a match is found, it returns true, indicating the URL is excluded.
*
* @param {URL} url - The URL to check.
* @returns {boolean} True if the URL is excluded by a domain rule, false otherwise.
*/
isExcludedByDomain(url) {
return this.excludedPatterns.some(
(pattern) => pattern.type === "domain" && url.hostname.includes(pattern.value)
);
}
/**
* Checks if a URL is from an external domain.
*
* This method compares the hostname of the given URL with the hostname of the current location.
* If they are not the same, it indicates the URL is from an external domain.
*
* @param {URL} url - The URL to check.
* @returns {boolean} True if the URL is from an external domain, false otherwise.
*/
isExternalDomain(url) {
return url.hostname !== location.hostname && url.hostname;
}
/**
* Creates an exclusion object based on the URL, element.
*
* @param {URL} url - The URL to create the exclusion object for.
* @param {Element} el - The element to create the exclusion object for.
* @returns {Object} An object with the URL's hostname, the element's tag name, and the reason.
*/
createExclusionObject(url, el) {
return { domain: url.hostname, elementType: el.tagName.toLowerCase() };
}
/**
* Returns an array of matched items, each item split into its domain and element type.
*
* This method iterates through the matchedItems set, splits each item into its domain and element type using the last hyphen as a delimiter,
* and returns an array of these split items.
*
* @returns {Array} An array of arrays, each containing a domain and an element type.
*/
getMatchedItems() {
return Array.from(this.matchedItems).map((item) => {
const lastHyphenIndex = item.lastIndexOf("-");
return [
item.substring(0, lastHyphenIndex),
// Domain
item.substring(lastHyphenIndex + 1)
// Element type
];
});
}
/**
* Returns the array of unique domain names that were found to be external.
*
* This method returns the result array, which contains a list of unique domain names that were identified as external during the analysis process.
*
* @returns {Array} An array of unique domain names.
*/
getResults() {
return this.result;
}
};
var BeaconPreconnectExternalDomain_default = BeaconPreconnectExternalDomain;
// src/Logger.js
var Logger = class {
constructor(enabled) {
this.enabled = enabled;
}
logMessage(label, msg = "") {
if (!this.enabled) {
return;
}
if (msg !== "") {
console.log(label, msg);
return;
}
console.log(label);
}
logColoredMessage(msg, color = "green") {
if (!this.enabled) {
return;
}
console.log(`%c${msg}`, `color: ${color};`);
}
};
var Logger_default = Logger;
// src/BeaconManager.js
var BeaconManager = class {
constructor(config) {
this.config = config;
this.lcpBeacon = null;
this.lrcBeacon = null;
this.preloadFontsBeacon = null;
this.preconnectExternalDomainBeacon = null;
this.infiniteLoopId = null;
this.errorCode = "";
this.logger = new Logger_default(this.config.debug);
}
async init() {
this.scriptTimer = /* @__PURE__ */ new Date();
if (!await this._isValidPreconditions()) {
this._finalize();
return;
}
if (Utils_default.isPageScrolled()) {
this.logger.logMessage("Bailing out because the page has been scrolled");
this._finalize();
return;
}
this.infiniteLoopId = setTimeout(() => {
this._handleInfiniteLoop();
}, 1e4);
const isGeneratedBefore = await this._getGeneratedBefore();
const shouldGenerateLcp = this.config.status.atf && (isGeneratedBefore === false || isGeneratedBefore.lcp === false);
const shouldGeneratelrc = this.config.status.lrc && (isGeneratedBefore === false || isGeneratedBefore.lrc === false);
const shouldGeneratePreloadFonts = this.config.status.preload_fonts && (isGeneratedBefore === false || isGeneratedBefore.preload_fonts === false);
const shouldGeneratePreconnectExternalDomain = this.config.status.preconnect_external_domain && (isGeneratedBefore === false || isGeneratedBefore.preconnect_external_domain === false);
if (shouldGenerateLcp) {
this.lcpBeacon = new BeaconLcp_default(this.config, this.logger);
await this.lcpBeacon.run();
} else {
this.logger.logMessage("Not running BeaconLcp because data is already available or feature is disabled");
}
if (shouldGeneratelrc) {
this.lrcBeacon = new BeaconLrc_default(this.config, this.logger);
await this.lrcBeacon.run();
} else {
this.logger.logMessage("Not running BeaconLrc because data is already available or feature is disabled");
}
if (shouldGeneratePreloadFonts) {
this.preloadFontsBeacon = new BeaconPreloadFonts_default(this.config, this.logger);
await this.preloadFontsBeacon.run();
} else {
this.logger.logMessage("Not running BeaconPreloadFonts because data is already available or feature is disabled");
}
if (shouldGeneratePreconnectExternalDomain) {
this.preconnectExternalDomainBeacon = new BeaconPreconnectExternalDomain_default(this.config, this.logger);
await this.preconnectExternalDomainBeacon.run();
} else {
this.logger.logMessage("Not running BeaconPreconnectExternalDomain because data is already available or feature is disabled");
}
if (shouldGenerateLcp || shouldGeneratelrc || shouldGeneratePreloadFonts || shouldGeneratePreconnectExternalDomain) {
this._saveFinalResultIntoDB();
} else {
this.logger.logMessage("Not saving results into DB as no beacon features ran.");
this._finalize();
}
}
async _isValidPreconditions() {
const threshold = {
width: this.config.width_threshold,
height: this.config.height_threshold
};
if (Utils_default.isNotValidScreensize(this.config.is_mobile, threshold)) {
this.logger.logMessage("Bailing out because screen size is not acceptable");
return false;
}
return true;
}
async _getGeneratedBefore() {
if (!Utils_default.isPageCached()) {
return false;
}
let data_check = new FormData();
data_check.append("action", "rocket_check_beacon");
data_check.append("rocket_beacon_nonce", this.config.nonce);
data_check.append("url", this.config.url);
data_check.append("is_mobile", this.config.is_mobile);
const beacon_data_response = await fetch(this.config.ajax_url, {
method: "POST",
credentials: "same-origin",
body: data_check
}).then((data) => data.json());
return beacon_data_response.data;
}
_saveFinalResultIntoDB() {
const results = {
lcp: this.lcpBeacon ? this.lcpBeacon.getResults() : null,
lrc: this.lrcBeacon ? this.lrcBeacon.getResults() : null,
preload_fonts: this.preloadFontsBeacon ? this.preloadFontsBeacon.getResults() : null,
preconnect_external_domain: this.preconnectExternalDomainBeacon ? this.preconnectExternalDomainBeacon.getResults() : null
};
const data = new FormData();
data.append("action", "rocket_beacon");
data.append("rocket_beacon_nonce", this.config.nonce);
data.append("url", this.config.url);
data.append("is_mobile", this.config.is_mobile);
data.append("status", this._getFinalStatus());
data.append("results", JSON.stringify(results));
fetch(this.config.ajax_url, {
method: "POST",
credentials: "same-origin",
body: data,
headers: {
"wpr-saas-no-intercept": true
}
}).then((response) => response.json()).then((data2) => {
this.logger.logMessage(data2.data.lcp);
}).catch((error) => {
this.logger.logMessage(error);
}).finally(() => {
this._finalize();
});
}
_getFinalStatus() {
if ("" !== this.errorCode) {
return this.errorCode;
}
const scriptTime = (/* @__PURE__ */ new Date() - this.scriptTimer) / 1e3;
if (10 <= scriptTime) {
return "timeout";
}
return "success";
}
_handleInfiniteLoop() {
this._saveFinalResultIntoDB();
}
_finalize() {
const beaconscript = document.querySelector('[data-name="wpr-wpr-beacon"]');
beaconscript.setAttribute("beacon-completed", "true");
clearTimeout(this.infiniteLoopId);
}
};
var BeaconManager_default = BeaconManager;
// src/BeaconEntryPoint.js
((rocket_beacon_data) => {
if (!rocket_beacon_data) {
return;
}
const instance = new BeaconManager_default(rocket_beacon_data);
if (document.readyState !== "loading") {
setTimeout(() => {
instance.init();
}, rocket_beacon_data.delay);
return;
}
document.addEventListener("DOMContentLoaded", () => {
setTimeout(() => {
instance.init();
}, rocket_beacon_data.delay);
});
})(window.rocket_beacon_data);
var BeaconEntryPoint_default = BeaconManager_default;
})();