Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/server/static-assets.ts
T

130 lines
4.9 KiB
TypeScript

import type { Stats } from "node:fs";
import { stat } from "node:fs/promises";
export type StaticContentEncoding = "br" | "gzip" | "identity";
export type StaticAssetRepresentation = {
encoding: StaticContentEncoding;
filePath: string;
stat: Stats;
};
export type StaticAssetMetadata = {
identity: StaticAssetRepresentation;
br?: StaticAssetRepresentation;
gzip?: StaticAssetRepresentation;
};
const metadataCache = new Map<string, Promise<StaticAssetMetadata | null>>();
export async function getStaticAssetMetadata(filePath: string): Promise<StaticAssetMetadata | null> {
const cached = metadataCache.get(filePath);
if (cached) {
return cached;
}
const pending = loadStaticAssetMetadata(filePath);
metadataCache.set(filePath, pending);
const metadata = await pending;
if (metadata === null) {
// Do not let attacker-controlled misses grow a process-lifetime cache.
metadataCache.delete(filePath);
}
return metadata;
}
async function loadStaticAssetMetadata(filePath: string): Promise<StaticAssetMetadata | null> {
const identityStat = await stat(filePath).catch(() => null);
if (!identityStat?.isFile()) {
return null;
}
const [brStat, gzipStat] = await Promise.all([stat(`${filePath}.br`).catch(() => null), stat(`${filePath}.gz`).catch(() => null)]);
return {
identity: { encoding: "identity", filePath, stat: identityStat },
...(brStat?.isFile() ? { br: { encoding: "br" as const, filePath: `${filePath}.br`, stat: brStat } } : {}),
...(gzipStat?.isFile() ? { gzip: { encoding: "gzip" as const, filePath: `${filePath}.gz`, stat: gzipStat } } : {}),
};
}
export function selectStaticRepresentation(metadata: StaticAssetMetadata, acceptEncoding: string | undefined): StaticAssetRepresentation {
const accepted = parseAcceptEncoding(acceptEncoding);
const candidates = [
metadata.br ? { quality: accepted.get("br") ?? accepted.get("*") ?? 0, representation: metadata.br, preference: 2 } : null,
metadata.gzip ? { quality: accepted.get("gzip") ?? accepted.get("*") ?? 0, representation: metadata.gzip, preference: 1 } : null,
]
.filter((candidate): candidate is NonNullable<typeof candidate> => candidate !== null && candidate.quality > 0)
.sort((left, right) => right.quality - left.quality || right.preference - left.preference);
return candidates[0]?.representation ?? metadata.identity;
}
function parseAcceptEncoding(value: string | undefined): Map<string, number> {
const accepted = new Map<string, number>();
for (const item of value?.split(",") ?? []) {
const [rawEncoding, ...parameters] = item.trim().toLowerCase().split(";");
if (!rawEncoding) continue;
let quality = 1;
for (const parameter of parameters) {
const match = /^q\s*=\s*(0(?:\.\d+)?|1(?:\.0+)?)$/.exec(parameter.trim());
if (match) {
quality = Number(match[1]);
}
}
accepted.set(rawEncoding, quality);
}
return accepted;
}
export function createStaticEtag(representation: StaticAssetRepresentation): string {
const size = representation.stat.size.toString(16);
const modified = Math.trunc(representation.stat.mtimeMs).toString(16);
return `W/"${size}-${modified}-${representation.encoding}"`;
}
export function isStaticAssetNotModified(input: { etag: string; ifModifiedSince?: string; ifNoneMatch?: string; mtimeMs: number }): boolean {
if (input.ifNoneMatch !== undefined) {
const expected = normalizeWeakEtag(input.etag);
return input.ifNoneMatch
.split(",")
.map((value) => value.trim())
.some((value) => value === "*" || normalizeWeakEtag(value) === expected);
}
if (input.ifModifiedSince === undefined) {
return false;
}
const since = Date.parse(input.ifModifiedSince);
if (!Number.isFinite(since)) {
return false;
}
return Math.floor(input.mtimeMs / 1000) <= Math.floor(since / 1000);
}
function normalizeWeakEtag(value: string): string {
return value.trim().replace(/^W\//i, "");
}
export function getStaticCacheControl(pathname: string, localeIsRevisioned: boolean): string {
if (pathname === "/assets" || pathname.startsWith("/assets/")) {
return "public, max-age=31536000, immutable";
}
if (pathname === "/locales" || pathname.startsWith("/locales/")) {
return localeIsRevisioned ? "public, max-age=31536000, immutable" : "public, max-age=300, must-revalidate, stale-while-revalidate=86400";
}
if (pathname === "/fonts" || pathname.startsWith("/fonts/") || pathname === "/images" || pathname.startsWith("/images/")) {
return "public, max-age=86400, stale-while-revalidate=604800";
}
return "public, max-age=0, must-revalidate";
}
export function hasCompressedRepresentation(metadata: StaticAssetMetadata): boolean {
return metadata.br !== undefined || metadata.gzip !== undefined;
}
/** Test-only hook for deterministic metadata-cache assertions. */
export function __resetStaticAssetMetadataCacheForTests(): void {
metadataCache.clear();
}