// kit's universal fetch inlines each read as a <script data-sveltekit-fetched> in the
// order the promises resolved. sorting them makes the html a function of the page's reads,
// not of which loopback fetch came back first
const FETCHED_MARKER = '<script type="application/json" data-sveltekit-fetched';
const FETCHED = /<script type="application\/json" data-sveltekit-fetched data-url="([^"]*)"[^>]*>[\s\S]*?<\/script>/g;
export const canonicalHtml = (html) => {
const start = html.indexOf(FETCHED_MARKER);
if (start === -1 || html.indexOf(FETCHED_MARKER, start + FETCHED_MARKER.length) === -1) return html;
const tail = html.slice(start);
const blocks = [...tail.matchAll(FETCHED)];
if (blocks.length < 2) return html;
const sorted = blocks.map((m) => m[0]).sort();
let at = 0;
let rebuilt = html.slice(0, start);
blocks.forEach((m, i) => {
rebuilt += tail.slice(at, m.index) + sorted[i];
at = m.index + m[0].length;
});
return rebuilt + tail.slice(at);
};
const HREF = /href="([^"#?]+)(\?[^"#]*)?/g;
const PAGE = /(?:^\?|&(?:amp;)?)page=([0-9]+)(?:&|$)/;
// same-repo routes linked from rendered html, decoded the way routeOf and the filesystem
// see them. a link to a later page of a list keeps its "?page=N", other queries go
export const linksOf = (html, origin, routeOf) => {
const found = new Set();
const seen = new Set();
for (const [, raw, query] of html.matchAll(HREF)) {
const page = Number(PAGE.exec(query ?? "")?.[1] ?? 1);
if (seen.has(raw + page)) continue;
seen.add(raw + page);
if (!raw.startsWith("/")) continue;
const href = raw.includes("&") ? raw.replaceAll("&", "&") : raw;
let pathname;
try {
pathname =
href.startsWith("//") || href.includes("/.") || href.includes("\\")
? decodeURI(new URL(href, origin).pathname)
: decodeURI(href);
} catch {
continue;
}
if (pathname.length > 1 && pathname.endsWith("/")) pathname = pathname.replace(/\/+$/, "") || "/";
const route = page > 1 ? `${pathname}?page=${page}` : pathname;
if (routeOf(route) !== null) found.add(route);
else if (page > 1 && routeOf(pathname) !== null) found.add(pathname);
}
return found;
};
// kit inlines every response a page's loads read; past this one is a payload the page should
// have trimmed or shared, so the summary counts it
export const INLINE_CAP = 32 * 1024;
const INLINED = /<script type="application\/json" data-sveltekit-fetched data-url="\/xrpc\/([^?"]+)[^>]*>(.*?)<\/script>/gs;
/** the inlined responses of a page past INLINE_CAP, by nsid and size */
export const inlinedOverCap = (html) =>
[...html.matchAll(INLINED)].filter((m) => m[2].length > INLINE_CAP).map((m) => ({ nsid: m[1], bytes: m[2].length }));