import { FinalizationRegistry, TypeError, uncurryThis, WeakMap, WeakMapPrototypeGet as get, WeakMapPrototypeSet as set, } from "ext:core/primordials"; import { markPromiseHandled, safePromise } from "ext:core/promises"; import { op_http_file_close, op_http_file_handle, op_http_file_read, op_http_serve_file, } from "ext:core/ops"; import { dictionary, state } from "ext:web/idl.js"; import { addListener, removeListener, signals } from "ext:web/events.js"; import type { AbortSignal } from "ext:web/events.js"; import { args, detachResponseHandle, path, responseHandle, } from "ext:file/main.js"; import { result } from "ext:file/errors.js"; import { messageState } from "ext:http_types/body.js"; import { fromPairs, headerEntries, Headers } from "ext:http_types/headers.js"; import { Response } from "ext:http_types/response.js"; import { enqueue, errorStream, finish, ReadableStream, streamCancel, } from "ext:webstreams/streams.js"; import type { ReadableByteStreamController } from "ext:webstreams/streams.js"; interface FileData { id: number | null; signal: AbortSignal; owner: ReadableStream> | null; onAbort: (() => void) | null; reading: boolean; } const files = new WeakMap, FileData>(), registry = new FinalizationRegistry((id) => op_http_file_close(id)), register = uncurryThis( (FinalizationRegistry.prototype as FinalizationRegistry).register, ), unregister = uncurryThis(FinalizationRegistry.prototype.unregister); function close(data: FileData) { if (data.id === null) return; const id = data.id; data.id = null; unregister(registry, data.owner!); removeListener(data.signal, "abort", data.onAbort); op_http_file_close(id); } function takeFile( stream: ReadableStream, signal: AbortSignal, ): number | null { const data = get(files, stream); if (!data || data.id === null || data.reading || data.signal !== signal) { return null; } const id = data.id; data.id = null; unregister(registry, data.owner!); removeListener(signal, "abort", data.onAbort); // Disturb the JS body without canceling the native body being transferred. markPromiseHandled(streamCancel(stream)); return id; } async function serveFile( request: unknown, source: unknown, options: unknown = undefined, ): Promise { const req = messageState(request, "request"), handle = responseHandle(source), filename = handle === null ? path(source) : null, settings = dictionary(options), headers = new Headers(settings.headers), consume = settings.consume, signal = req.signal, aborted = state(signals, signal); if (consume !== undefined && typeof consume !== "boolean") { throw new TypeError("consume must be boolean"); } if (aborted.aborted) throw aborted.reason; const requestHeaders = headerEntries(req.headers), extraHeaders = headerEntries(headers); let nativeSource: string | number | null = filename; if (handle !== null) { nativeSource = result(op_http_file_handle(args(handle, consume !== false))); if (consume !== false) detachResponseHandle(source); } const prepared = result( await safePromise(op_http_serve_file(args( req.method, requestHeaders, nativeSource!, extraHeaders, ))), ); const id = prepared[2]; if (aborted.aborted) { if (id !== null) op_http_file_close(id); throw aborted.reason; } const data: FileData = { id, signal, owner: null, onAbort: null, reading: false, }; try { const init = { __proto__: null, status: prepared[0], headers: fromPairs(prepared[1]), }; if (id === null) return new Response(null, init); let controller: ReadableByteStreamController; const stream = new ReadableStream>({ __proto__: null, type: "bytes", start(value) { controller = value; }, async pull(controller) { data.reading = true; try { const chunk = result(await safePromise(op_http_file_read(data.id!))); if (data.id === null) return; enqueue(controller, chunk[0]); if (chunk[1]) { finish(controller); close(data); } } catch (error) { if (data.id !== null) errorStream(controller, error); close(data); } }, cancel() { close(data); }, }, { __proto__: null, highWaterMark: 0 }); data.owner = stream; data.onAbort = () => { errorStream(controller, aborted.reason); close(data); }; set(files, stream, data); register(registry, stream, id, stream); addListener(signal, "abort", data.onAbort, { once: true }); const response = new Response(stream, init); if (aborted.aborted) throw aborted.reason; return response; } catch (error) { if (data.owner === null) { if (id !== null) op_http_file_close(id); } else close(data); throw error; } } export { serveFile, takeFile };