chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* §6.4–§6.5. The only boot-time JSON reader.
|
||||
*
|
||||
* `response.json()` and unbounded `response.text()` are prohibited: a hostile or
|
||||
* misconfigured origin must not be able to amplify boot memory, and an HTML
|
||||
* error page must not reach `JSON.parse` as if it were configuration.
|
||||
*/
|
||||
|
||||
export type BootJsonOperation = "RUNTIME_CONFIG" | "RELEASE_MANIFEST";
|
||||
|
||||
export interface BootJsonPolicy {
|
||||
readonly operation: BootJsonOperation;
|
||||
readonly maximumBytes: number;
|
||||
readonly totalDeadlineMs: 5_000;
|
||||
}
|
||||
|
||||
export const BOOT_JSON_POLICIES = Object.freeze({
|
||||
RUNTIME_CONFIG: Object.freeze({
|
||||
operation: "RUNTIME_CONFIG" as const,
|
||||
maximumBytes: 65_536,
|
||||
totalDeadlineMs: 5_000 as const,
|
||||
}),
|
||||
RELEASE_MANIFEST: Object.freeze({
|
||||
operation: "RELEASE_MANIFEST" as const,
|
||||
maximumBytes: 1_048_576,
|
||||
totalDeadlineMs: 5_000 as const,
|
||||
}),
|
||||
} satisfies Readonly<Record<BootJsonOperation, BootJsonPolicy>>);
|
||||
|
||||
export type BootLoadFailure =
|
||||
| "FETCH_FAILED"
|
||||
| "TIMEOUT"
|
||||
| "HTTP_STATUS_INVALID"
|
||||
| "CONTENT_TYPE_INVALID"
|
||||
| "BODY_TOO_LARGE"
|
||||
| "UTF8_INVALID"
|
||||
| "JSON_INVALID"
|
||||
| "SHAPE_INVALID"
|
||||
| "SECRET_NAME_REJECTED"
|
||||
| "SCHEMA_INVALID"
|
||||
| "BUILD_MISMATCH"
|
||||
| "RELEASE_MISMATCH"
|
||||
| "ASSET_MISMATCH"
|
||||
| "CONTRACT_SET_MISMATCH";
|
||||
|
||||
export type BootJsonOutcome =
|
||||
| Readonly<{ ok: true; value: Readonly<Record<string, unknown>> }>
|
||||
| Readonly<{ ok: false; failure: BootLoadFailure }>;
|
||||
|
||||
export type ReadBoundedBootJsonOptions = Readonly<{
|
||||
fetcher?: typeof fetch;
|
||||
signal?: AbortSignal;
|
||||
}>;
|
||||
|
||||
function isJsonMediaType(headerValue: string | null): boolean {
|
||||
if (!headerValue) return false;
|
||||
const essence = headerValue.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
||||
return essence === "application/json" || essence.endsWith("+json");
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
export async function readBoundedBootJson(
|
||||
url: string,
|
||||
policy: BootJsonPolicy,
|
||||
options: ReadBoundedBootJsonOptions = {},
|
||||
): Promise<BootJsonOutcome> {
|
||||
if (options.signal?.aborted) return fail("FETCH_FAILED");
|
||||
|
||||
const fetcher = options.fetcher ?? fetch;
|
||||
const controller = new AbortController();
|
||||
let timedOut = false;
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
controller.abort();
|
||||
}, policy.totalDeadlineMs);
|
||||
const forwardAbort = () => controller.abort();
|
||||
options.signal?.addEventListener("abort", forwardAbort, { once: true });
|
||||
|
||||
try {
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetcher(url, {
|
||||
method: "GET",
|
||||
cache: "no-store",
|
||||
credentials: "same-origin",
|
||||
redirect: "error",
|
||||
referrerPolicy: "no-referrer",
|
||||
headers: { Accept: "application/json" },
|
||||
signal: controller.signal,
|
||||
});
|
||||
} catch {
|
||||
return fail(timedOut ? "TIMEOUT" : "FETCH_FAILED");
|
||||
}
|
||||
|
||||
if (response.status !== 200 || response.redirected) {
|
||||
await discard(response);
|
||||
return fail("HTTP_STATUS_INVALID");
|
||||
}
|
||||
if (!isJsonMediaType(response.headers.get("content-type"))) {
|
||||
await discard(response);
|
||||
return fail("CONTENT_TYPE_INVALID");
|
||||
}
|
||||
|
||||
const declared = Number(response.headers.get("content-length"));
|
||||
if (Number.isFinite(declared) && declared > policy.maximumBytes) {
|
||||
await discard(response);
|
||||
return fail("BODY_TOO_LARGE");
|
||||
}
|
||||
|
||||
const bytes = await readBoundedBytes(response, policy.maximumBytes, controller);
|
||||
if (bytes === "TOO_LARGE") return fail("BODY_TOO_LARGE");
|
||||
if (bytes === "STREAM_FAILED") return fail(timedOut ? "TIMEOUT" : "FETCH_FAILED");
|
||||
|
||||
let text: string;
|
||||
try {
|
||||
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
||||
} catch {
|
||||
return fail("UTF8_INVALID");
|
||||
}
|
||||
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(text);
|
||||
} catch {
|
||||
return fail("JSON_INVALID");
|
||||
}
|
||||
if (!isRecord(parsed)) return fail("SHAPE_INVALID");
|
||||
|
||||
return Object.freeze({ ok: true as const, value: parsed });
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
options.signal?.removeEventListener("abort", forwardAbort);
|
||||
}
|
||||
}
|
||||
|
||||
async function readBoundedBytes(
|
||||
response: Response,
|
||||
maximumBytes: number,
|
||||
controller: AbortController,
|
||||
): Promise<Uint8Array | "TOO_LARGE" | "STREAM_FAILED"> {
|
||||
const body = response.body;
|
||||
if (!body) {
|
||||
// A body-less 200 cannot satisfy any boot document.
|
||||
return new Uint8Array(0);
|
||||
}
|
||||
const reader = body.getReader();
|
||||
const chunks: Uint8Array[] = [];
|
||||
let total = 0;
|
||||
try {
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
if (!value) continue;
|
||||
total += value.byteLength;
|
||||
if (total > maximumBytes) {
|
||||
await reader.cancel().catch(() => {});
|
||||
controller.abort();
|
||||
return "TOO_LARGE";
|
||||
}
|
||||
chunks.push(value);
|
||||
}
|
||||
} catch {
|
||||
await reader.cancel().catch(() => {});
|
||||
return "STREAM_FAILED";
|
||||
}
|
||||
|
||||
const output = new Uint8Array(total);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
output.set(chunk, offset);
|
||||
offset += chunk.byteLength;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
async function discard(response: Response): Promise<void> {
|
||||
try {
|
||||
await response.body?.cancel();
|
||||
} catch {
|
||||
// Cancelling an already-settled body is not a boot failure.
|
||||
}
|
||||
}
|
||||
|
||||
function fail(failure: BootLoadFailure): BootJsonOutcome {
|
||||
return Object.freeze({ ok: false as const, failure });
|
||||
}
|
||||
Reference in New Issue
Block a user