page-load-type
Version:
Determines page load type for websites using Signed Exchanges
144 lines (125 loc) • 4 kB
JavaScript
const SxgStatusConfig = {
scriptPath: '/sxg/resolve-status.js',
eventName: 'SxgStatusResolved',
eventProperty: 'subresources',
};
function getPageLoadType(
{
// Configuration
sxgStatusConfig = SxgStatusConfig,
// Dependencies
resolveSxgStatus = ResolveSxgStatus,
sxgUsed = SxgUsed,
browserCached = BrowserCached,
fromSxgCache = FromSxgCache,
cfCacheUsed = CfCacheUsed,
earlyHintsUsed = EarlyHintsUsed,
prefetched = Prefetched,
} = {}) {
return new Promise((resolve) => {
if (sxgUsed()) {
if (browserCached()) {
resolveSxgStatus({ sxgStatusConfig: sxgStatusConfig }).
then(() => resolve('sxg_complete_prefetch')).
catch(() => resolve('sxg_document_prefetch'));
} else {
resolve('sxg_document_on_demand');
}
} else { // non SXG
if (fromSxgCache()) {
if (cfCacheUsed()) {
resolve('sxg_fallback_on_demand_edge');
} else if (earlyHintsUsed()) {
resolve('sxg_fallback_on_demand_hints');
} else {
resolve('sxg_fallback_on_demand_origin');
}
} else {
if (browserCached()) {
resolve('browser_cache');
} else if (prefetched()) {
resolve('document_prefetch');
} else {
if (cfCacheUsed()) {
resolve('document_on_demand_edge');
} else if (earlyHintsUsed()) {
resolve('document_on_demand_hints');
} else {
resolve('document_on_demand_origin');
}
}
}
}
})
}
// Default dependencies (upper camel case)
let sxgSubresources = undefined;
let sxgNeverResolved = true;
function ResolveSxgStatus({ scriptLoader = ScriptLoader, sxgStatusConfig = SxgStatusConfig } = {}) {
return new Promise((resolve, reject) => {
if (sxgSubresources !== undefined) return sxgSubresources ? resolve() : reject();
document.addEventListener(sxgStatusConfig.eventName, e => {
sxgSubresources = e.detail[sxgStatusConfig.eventProperty];
sxgSubresources ? resolve() : reject();
}, { once: true });
if (sxgNeverResolved) scriptLoader(sxgStatusConfig.scriptPath);
sxgNeverResolved = false;
});
}
function ScriptLoader(path) {
const script = document.createElement('script');
script.src = path;
document.head.appendChild(script);
}
function EarlyHintsUsed() {
for (const resource of performance.getEntriesByType("resource")) {
if (resource.initiatorType === "early-hints") return true;
}
return false;
}
function CfCacheUsed() {
return ['HIT', 'STALE', 'UPDATING'].indexOf(cfCacheStatus()) !== -1;
}
function FromSxgCache() {
return referrerHostMatches(/^.+\.webpkgcache\.com$/i);
}
function BrowserCached() {
try {
const navEntry = performance.getEntriesByType("navigation")[0];
return navEntry.deliveryType === 'cache';
} catch (e) {
return false;
}
}
function Prefetched() {
try {
const navEntry = performance.getEntriesByType("navigation")[0];
return navEntry.deliveryType === 'navigational-prefetch';
} catch (e) {
return false;
}
}
function SxgUsed() {
return !!window.isSXG;
}
// Other functions (lower camel case)
function cfCacheStatus() {
const navEntry = performance.getEntriesByType('navigation')[0];
if (!navEntry || !navEntry.serverTiming) return null;
const cfCacheMetric = navEntry.serverTiming.find(metric => metric.name === "cfCacheStatus");
return cfCacheMetric ? cfCacheMetric.description : null;
}
function referrerHostMatches(regex) {
if (!document.referrer) return false;
try {
const navEntry = performance.getEntriesByType("navigation")[0];
const isNewNavigation = navEntry && navEntry.type === 'navigate';
const referrer = new URL(document.referrer);
const isGoogle = regex.test(referrer.hostname);
return isGoogle && isNewNavigation;
} catch (e) {
return false;
}
}
export default getPageLoadType;
export { ResolveSxgStatus as resolveSxgStatus };