import { createHash } from "node:crypto"; export type PullRequest = { id: string; paths: string[]; requestedAt: number; deadlineAt?: number; }; export function parsePullRequest( value: unknown, fileName: string, ): PullRequest { const item = value as Partial | null; if ( !item || typeof item.id !== "string" || !/^[a-zA-Z0-9-]+$/.test(item.id) || fileName !== `request-${item.id}.json` || !Array.isArray(item.paths) || !item.paths.length || item.paths.length > 1000 || item.paths.some( (path) => typeof path !== "string" || !path || path.length > 4096, ) || typeof item.requestedAt !== "number" || !Number.isFinite(item.requestedAt) || (item.deadlineAt !== undefined && (typeof item.deadlineAt !== "number" || !Number.isFinite(item.deadlineAt))) ) { throw new Error("pull_invalid_request"); } return item as PullRequest; } export function pullTimeRemaining(deadline: number): number { const remaining = deadline - Date.now(); if (remaining <= 0) throw new Error("pull_timeout"); return remaining; } export async function withinPullDeadline( operation: (signal: AbortSignal) => Promise, deadline: number, ): Promise { const remaining = pullTimeRemaining(deadline); const controller = new AbortController(); let timer: ReturnType | undefined; try { return await Promise.race([ operation(controller.signal), new Promise((_, reject) => { timer = setTimeout(() => { controller.abort(); reject(new Error("pull_timeout")); }, remaining); }), ]); } finally { clearTimeout(timer); } } /** A successful pull means verified ORIGINAL bytes finished writing, not just * a fileTree row or a URL existed. No signed URLs/vendor bodies in errors. */ export async function downloadRemoteFile(args: { resolveUrl: () => Promise; expectedHash: string; deadline: number; maxBytes?: number; }): Promise { const url = await withinPullDeadline(args.resolveUrl, args.deadline); if (!url) throw new Error("pull_original_missing"); return withinPullDeadline(async (signal) => { try { const response = await fetch(url, { signal }); if (!response.ok) { void response.body?.cancel().catch(() => undefined); throw new Error("pull_http_error: " + response.status); } if (!response.body) throw new Error("pull_empty_body"); const reader = response.body.getReader(); const chunks: Uint8Array[] = []; let size = 0; const hash = createHash("sha256"); try { for (;;) { const { done, value } = await reader.read(); signal.throwIfAborted(); if (done) break; size += value.length; if (size > (args.maxBytes ?? 512 * 1024 * 1024)) { void reader.cancel().catch(() => undefined); throw new Error("pull_too_large"); } hash.update(value); chunks.push(value); } } finally { reader.releaseLock(); } if (hash.digest("hex") !== args.expectedHash) throw new Error("pull_hash_mismatch"); return Buffer.concat(chunks, size); } catch (error) { if (error instanceof Error && error.message.startsWith("pull_")) throw error; throw new Error(signal.aborted ? "pull_timeout" : "pull_fetch_failed"); } }, args.deadline); }