"use strict";
((window) => {
const core = window.Deno.core;
const ops = core.ops;
const webidl = window.__bootstrap.webidl;
const { byteLowerCase } = window.__bootstrap.infra;
const { BlobPrototype } = window.__bootstrap.file;
const { errorReadableStream, ReadableStreamPrototype, readableStreamForRid } =
window.__bootstrap.streams;
const { InnerBody, extractBody } = window.__bootstrap.fetchBody;
const {
toInnerRequest,
toInnerResponse,
fromInnerResponse,
redirectStatus,
nullBodyStatus,
networkError,
abortedNetworkError,
processUrlList,
} = window.__bootstrap.fetch;
const abortSignal = window.__bootstrap.abortSignal;
const {
ArrayPrototypePush,
ArrayPrototypeSplice,
ArrayPrototypeFilter,
ArrayPrototypeIncludes,
ObjectPrototypeIsPrototypeOf,
Promise,
PromisePrototypeThen,
PromisePrototypeCatch,
SafeArrayIterator,
String,
StringPrototypeStartsWith,
StringPrototypeToLowerCase,
TypeError,
Uint8Array,
Uint8ArrayPrototype,
WeakMap,
WeakMapPrototypeDelete,
WeakMapPrototypeGet,
WeakMapPrototypeHas,
WeakMapPrototypeSet,
} = window.__bootstrap.primordials;
const REQUEST_BODY_HEADER_NAMES = [
"content-encoding",
"content-language",
"content-location",
"content-type",
];
const requestBodyReaders = new WeakMap();
function opFetch(method, url, headers, clientRid, hasBody, bodyLength, body) {
return ops.op_fetch(
method,
url,
headers,
clientRid,
hasBody,
bodyLength,
body,
);
}
function opFetchSend(rid) {
return core.opAsync("op_fetch_send", rid);
}
function createResponseBodyStream(responseBodyRid, terminator) {
const readable = readableStreamForRid(responseBodyRid);
function onAbort() {
errorReadableStream(readable, terminator.reason);
core.tryClose(responseBodyRid);
}
terminator[abortSignal.add](onAbort);
return readable;
}
async function mainFetch(req, recursive, terminator) {
if (req.blobUrlEntry !== null) {
if (req.method !== "GET") {
throw new TypeError("Blob URL fetch only supports GET method.");
}
const body = new InnerBody(req.blobUrlEntry.stream());
terminator[abortSignal.add](() => body.error(terminator.reason));
processUrlList(req.urlList, req.urlListProcessed);
return {
headerList: [
["content-length", String(req.blobUrlEntry.size)],
["content-type", req.blobUrlEntry.type],
],
status: 200,
statusMessage: "OK",
body,
type: "basic",
url() {
if (this.urlList.length == 0) return null;
return this.urlList[this.urlList.length - 1];
},
urlList: recursive
? []
: [...new SafeArrayIterator(req.urlListProcessed)],
};
}
let reqBody = null;
if (req.body !== null) {
if (
ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
req.body.streamOrStatic,
)
) {
if (
req.body.length === null ||
ObjectPrototypeIsPrototypeOf(BlobPrototype, req.body.source)
) {
reqBody = req.body.stream;
} else {
const reader = req.body.stream.getReader();
WeakMapPrototypeSet(requestBodyReaders, req, reader);
const r1 = await reader.read();
if (r1.done) {
reqBody = new Uint8Array(0);
} else {
reqBody = r1.value;
const r2 = await reader.read();
if (!r2.done) throw new TypeError("Unreachable");
}
WeakMapPrototypeDelete(requestBodyReaders, req);
}
} else {
req.body.streamOrStatic.consumed = true;
reqBody = req.body.streamOrStatic.body;
reqBody = typeof reqBody === "string" ? core.encode(reqBody) : reqBody;
}
}
const { requestRid, requestBodyRid, cancelHandleRid } = opFetch(
req.method,
req.currentUrl(),
req.headerList,
req.clientRid,
reqBody !== null,
req.body?.length,
ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, reqBody)
? reqBody
: null,
);
function onAbort() {
if (cancelHandleRid !== null) {
core.tryClose(cancelHandleRid);
}
if (requestBodyRid !== null) {
core.tryClose(requestBodyRid);
}
}
terminator[abortSignal.add](onAbort);
let requestSendError;
let requestSendErrorSet = false;
if (requestBodyRid !== null) {
if (
reqBody === null ||
!ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, reqBody)
) {
throw new TypeError("Unreachable");
}
const reader = reqBody.getReader();
WeakMapPrototypeSet(requestBodyReaders, req, reader);
(async () => {
let done = false;
while (!done) {
let val;
try {
const res = await reader.read();
done = res.done;
val = res.value;
} catch (err) {
if (terminator.aborted) break;
requestSendError = err;
requestSendErrorSet = true;
break;
}
if (done) break;
if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, val)) {
const error = new TypeError(
"Item in request body ReadableStream is not a Uint8Array",
);
await reader.cancel(error);
requestSendError = error;
requestSendErrorSet = true;
break;
}
try {
await core.writeAll(requestBodyRid, val);
} catch (err) {
if (terminator.aborted) break;
await reader.cancel(err);
requestSendError = err;
requestSendErrorSet = true;
break;
}
}
if (done && !terminator.aborted) {
try {
await core.shutdown(requestBodyRid);
} catch (err) {
if (!terminator.aborted) {
requestSendError = err;
requestSendErrorSet = true;
}
}
}
WeakMapPrototypeDelete(requestBodyReaders, req);
core.tryClose(requestBodyRid);
})();
}
let resp;
try {
resp = await opFetchSend(requestRid);
} catch (err) {
if (terminator.aborted) return;
if (requestSendErrorSet) {
throw new TypeError("Failed to fetch: request body stream errored", {
cause: requestSendError,
});
}
throw err;
} finally {
if (cancelHandleRid !== null) {
core.tryClose(cancelHandleRid);
}
}
if (terminator.aborted) return abortedNetworkError();
processUrlList(req.urlList, req.urlListProcessed);
const response = {
headerList: resp.headers,
status: resp.status,
body: null,
statusMessage: resp.statusText,
type: "basic",
url() {
if (this.urlList.length == 0) return null;
return this.urlList[this.urlList.length - 1];
},
urlList: req.urlListProcessed,
};
if (redirectStatus(resp.status)) {
switch (req.redirectMode) {
case "error":
core.close(resp.responseRid);
return networkError(
"Encountered redirect while redirect mode is set to 'error'",
);
case "follow":
core.close(resp.responseRid);
return httpRedirectFetch(req, response, terminator);
case "manual":
break;
}
}
if (nullBodyStatus(response.status)) {
core.close(resp.responseRid);
} else {
if (req.method === "HEAD" || req.method === "CONNECT") {
response.body = null;
core.close(resp.responseRid);
} else {
response.body = new InnerBody(
createResponseBodyStream(resp.responseRid, terminator),
);
}
}
if (recursive) return response;
if (response.urlList.length === 0) {
processUrlList(req.urlList, req.urlListProcessed);
response.urlList = [...new SafeArrayIterator(req.urlListProcessed)];
}
return response;
}
function httpRedirectFetch(request, response, terminator) {
const locationHeaders = ArrayPrototypeFilter(
response.headerList,
(entry) => byteLowerCase(entry[0]) === "location",
);
if (locationHeaders.length === 0) {
return response;
}
const locationURL = new URL(
locationHeaders[0][1],
response.url() ?? undefined,
);
if (locationURL.hash === "") {
locationURL.hash = request.currentUrl().hash;
}
if (locationURL.protocol !== "https:" && locationURL.protocol !== "http:") {
return networkError("Can not redirect to a non HTTP(s) url");
}
if (request.redirectCount === 20) {
return networkError("Maximum number of redirects (20) reached");
}
request.redirectCount++;
if (
response.status !== 303 &&
request.body !== null &&
request.body.source === null
) {
return networkError(
"Can not redeliver a streaming request body after a redirect",
);
}
if (
((response.status === 301 || response.status === 302) &&
request.method === "POST") ||
(response.status === 303 &&
request.method !== "GET" &&
request.method !== "HEAD")
) {
request.method = "GET";
request.body = null;
for (let i = 0; i < request.headerList.length; i++) {
if (
ArrayPrototypeIncludes(
REQUEST_BODY_HEADER_NAMES,
byteLowerCase(request.headerList[i][0]),
)
) {
ArrayPrototypeSplice(request.headerList, i, 1);
i--;
}
}
}
if (request.body !== null) {
const res = extractBody(request.body.source);
request.body = res.body;
}
ArrayPrototypePush(request.urlList, () => locationURL.href);
return mainFetch(request, true, terminator);
}
function fetch(input, init = {}) {
let opPromise = undefined;
const result = new Promise((resolve, reject) => {
const prefix = "Failed to call 'fetch'";
webidl.requiredArguments(arguments.length, 1, { prefix });
const requestObject = new Request(input, init);
const request = toInnerRequest(requestObject);
if (requestObject.signal.aborted) {
reject(abortFetch(request, null, requestObject.signal.reason));
return;
}
let responseObject = null;
let locallyAborted = false;
function onabort() {
locallyAborted = true;
reject(
abortFetch(request, responseObject, requestObject.signal.reason),
);
}
requestObject.signal[abortSignal.add](onabort);
if (!requestObject.headers.has("Accept")) {
ArrayPrototypePush(request.headerList, ["Accept", "*/*"]);
}
if (!requestObject.headers.has("Accept-Language")) {
ArrayPrototypePush(request.headerList, ["Accept-Language", "*"]);
}
opPromise = PromisePrototypeCatch(
PromisePrototypeThen(
mainFetch(request, false, requestObject.signal),
(response) => {
if (locallyAborted) return;
if (response.aborted) {
reject(
abortFetch(
request,
responseObject,
requestObject.signal.reason,
),
);
requestObject.signal[abortSignal.remove](onabort);
return;
}
if (response.type === "error") {
const err = new TypeError(
"Fetch failed: " + (response.error ?? "unknown error"),
);
reject(err);
requestObject.signal[abortSignal.remove](onabort);
return;
}
responseObject = fromInnerResponse(response, "immutable");
resolve(responseObject);
requestObject.signal[abortSignal.remove](onabort);
},
),
(err) => {
reject(err);
requestObject.signal[abortSignal.remove](onabort);
},
);
});
if (opPromise) {
PromisePrototypeCatch(result, () => {});
return (async function fetch() {
await opPromise;
return result;
})();
}
return result;
}
function abortFetch(request, responseObject, error) {
if (request.body !== null) {
if (WeakMapPrototypeHas(requestBodyReaders, request)) {
WeakMapPrototypeGet(requestBodyReaders, request).cancel(error);
} else {
request.body.cancel(error);
}
}
if (responseObject !== null) {
const response = toInnerResponse(responseObject);
if (response.body !== null) response.body.error(error);
}
return error;
}
function handleWasmStreaming(source, rid) {
try {
const res = webidl.converters["Response"](source, {
prefix: "Failed to call 'WebAssembly.compileStreaming'",
context: "Argument 1",
});
if (!StringPrototypeStartsWith(res.url, "file://")) {
const contentType = res.headers.get("Content-Type");
if (
typeof contentType !== "string" ||
StringPrototypeToLowerCase(contentType) !== "application/wasm"
) {
throw new TypeError("Invalid WebAssembly content type.");
}
}
if (!res.ok) {
throw new TypeError(`HTTP status code ${res.status}`);
}
ops.op_wasm_streaming_set_url(rid, res.url);
if (res.body !== null) {
PromisePrototypeThen(
(async () => {
const reader = res.body.getReader();
while (true) {
const { value: chunk, done } = await reader.read();
if (done) break;
ops.op_wasm_streaming_feed(rid, chunk);
}
})(),
() => core.close(rid),
(err) => core.abortWasmStreaming(rid, err),
);
} else {
core.close(rid);
}
} catch (err) {
core.abortWasmStreaming(rid, err);
}
}
window.__bootstrap.fetch ??= {};
window.__bootstrap.fetch.fetch = fetch;
window.__bootstrap.fetch.handleWasmStreaming = handleWasmStreaming;
})(this);