import crypto from "node:crypto"; import fs from "node:fs/promises"; import { fileURLToPath } from "node:url"; export const VIEWER_ASSET_PREFIX = "/plugins/diffs/assets/"; export const VIEWER_LOADER_PATH = `${VIEWER_ASSET_PREFIX}viewer.js`; export const VIEWER_RUNTIME_PATH = `${VIEWER_ASSET_PREFIX}viewer-runtime.js`; const VIEWER_RUNTIME_FILE_URL = new URL("../assets/viewer-runtime.js", import.meta.url); export type ServedViewerAsset = { body: string | Buffer; contentType: string; }; type RuntimeAssetCache = { mtimeMs: number; runtimeBody: Buffer; loaderBody: string; }; let runtimeAssetCache: RuntimeAssetCache | null = null; export async function getServedViewerAsset(pathname: string): Promise { if (pathname !== VIEWER_LOADER_PATH && pathname !== VIEWER_RUNTIME_PATH) { return null; } const assets = await loadViewerAssets(); if (pathname === VIEWER_LOADER_PATH) { return { body: assets.loaderBody, contentType: "text/javascript; charset=utf-8", }; } if (pathname === VIEWER_RUNTIME_PATH) { return { body: assets.runtimeBody, contentType: "text/javascript; charset=utf-8", }; } return null; } async function loadViewerAssets(): Promise { const runtimePath = fileURLToPath(VIEWER_RUNTIME_FILE_URL); const runtimeStat = await fs.stat(runtimePath); if (runtimeAssetCache && runtimeAssetCache.mtimeMs === runtimeStat.mtimeMs) { return runtimeAssetCache; } const runtimeBody = await fs.readFile(runtimePath); const hash = crypto.createHash("sha1").update(runtimeBody).digest("hex").slice(0, 12); runtimeAssetCache = { mtimeMs: runtimeStat.mtimeMs, runtimeBody, loaderBody: `import "${VIEWER_RUNTIME_PATH}?v=${hash}";\n`, }; return runtimeAssetCache; }