import { n as replaceTemplateVariables } from "./string-template-sS5PtY6T.js";
import { t as buildRequestSecurity } from "./build-request-security-BQjNr2MD.js";
import { i as isElectron, r as setRequestCookies, t as getCookieHeader } from "./set-request-cookies-BJJhsU4A.js";
import { i as isTextMediaType } from "./consts-CyGRb9dl.js";
import { canMethodHaveBody } from "@scalar/oas-utils/helpers";
import { isDefined as isDefined$1 } from "@scalar/helpers/array/is-defined";
import { httpStatusCodes as httpStatusCodes$1 } from "@scalar/helpers/http/http-status-codes";
import { mergeUrls as mergeUrls$1 } from "@scalar/helpers/url/merge-urls";
import { redirectToProxy, shouldUseProxy } from "@scalar/helpers/url/redirect-to-proxy";
import MimeTypeParser from "whatwg-mimetype";
//#region src/libs/errors.ts
/** Centralized list of all error messages */
var ERRORS = {
	BUILDING_REQUEST_FAILED: "An error occurred while building the request",
	DEFAULT: "An unknown error has occurred.",
	INVALID_URL: "The URL seems to be invalid. Try adding a valid URL.",
	INVALID_HEADER: "There is an invalid header present, please double check your params.",
	MISSING_FILE: "File uploads are not saved in history, you must re-upload the file.",
	REQUEST_ABORTED: "The request has been cancelled",
	REQUEST_FAILED: "An error occurred while making the request",
	URL_EMPTY: "The address bar input seems to be empty. Try adding a URL.",
	ON_BEFORE_REQUEST_FAILED: "onBeforeRequest request hook failed"
};
/** Normalizes caught error into an error instance */
var normalizeError = (e, defaultMessage = ERRORS.DEFAULT) => {
	console.error(e);
	if (e instanceof Error) {
		e.message = prettyErrorMessage(e.message);
		return e;
	}
	if (typeof e === "string") return new Error(prettyErrorMessage(e));
	return new Error(defaultMessage);
};
/** Takes javascript errors and returns a prettier message */
var prettyErrorMessage = (message) => {
	if (message === `Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'.`) return ERRORS.MISSING_FILE;
	if (message === `Failed to construct 'URL': Invalid URL`) return ERRORS.INVALID_URL;
	if (message === `Failed to execute 'fetch' on 'Window': Invalid name`) return ERRORS.INVALID_HEADER;
	return message;
};
//#endregion
//#region src/libs/normalize-headers.ts
/**
* Normalize headers:
*
* - Electron modifies the headers to allow CORS, this function hides the modifications
* - Restores original headers
* - Normalizes the header keys
* - Sorts headers alphabetically
* - converts to an object
*
*/
var normalizeHeaders = (_headers, removeProxyHeaders = false) => {
	const headers = Object.fromEntries(_headers);
	if (removeProxyHeaders) [
		"Access-Control-Allow-Credentials",
		"Access-Control-Allow-Headers",
		"Access-Control-Allow-Methods",
		"Access-Control-Allow-Origin",
		"Access-Control-Expose-Headers"
	].map((header) => header.toLowerCase()).forEach((header) => delete headers[header]);
	/** Exact key of the modified headers header */
	const modifiedHeaderKey = Object.keys(headers).find((key) => key.toLowerCase() === "x-scalar-modified-headers");
	/** List of modified headers */
	const modifiedHeaders = modifiedHeaderKey ? headers[modifiedHeaderKey]?.toString().split(", ")?.map((value) => value.toLowerCase()) ?? [] : [];
	Object.keys(headers).forEach((key) => {
		if (modifiedHeaders.includes(key.toLowerCase())) delete headers[key];
	});
	if (modifiedHeaderKey) delete headers[modifiedHeaderKey];
	Object.keys(headers).forEach((key) => {
		const ORIGINAL_KEY_REGEX = /^x-scalar-original-/i;
		if (ORIGINAL_KEY_REGEX.test(key)) {
			const originalKey = key.replace(ORIGINAL_KEY_REGEX, "");
			if (headers[key]) {
				headers[originalKey] = headers[key];
				delete headers[key];
			}
		}
	});
	Object.keys(headers).forEach((key) => {
		const formattedKey = formatHeaderKey(key);
		if (key !== formattedKey && headers[key]) {
			headers[formattedKey] = headers[key];
			delete headers[key];
		}
	});
	return Object.fromEntries(Object.entries(headers).sort(([a], [b]) => a.localeCompare(b)));
};
/** Make the first letter and all letters after a dash uppercase */
var formatHeaderKey = (key) => key.split("-").map((word) => {
	return word.charAt(0).toUpperCase() + word.toLowerCase().slice(1);
}).join("-");
//#endregion
//#region src/libs/send-request/create-fetch-body.ts
/**
* Create the fetch request body from an example
*
* TODO: Should we be setting the content type headers here?
* If so we must allow the user to override the content type header
*/
function createFetchBody(method, example, env) {
	if (!canMethodHaveBody(method)) return {
		body: void 0,
		contentType: void 0
	};
	if (example.body.activeBody === "formData" && example.body.formData) {
		const contentType = example.body.formData.encoding === "form-data" ? "multipart/form-data" : "application/x-www-form-urlencoded";
		const form = example.body.formData.encoding === "form-data" ? new FormData() : new URLSearchParams();
		example.body.formData.value.forEach((entry) => {
			if (!entry.enabled || !entry.key) return;
			if (entry.file && form instanceof FormData) form.append(entry.key, entry.file, entry.file.name);
			else if (entry.value !== void 0) form.append(entry.key, replaceTemplateVariables(entry.value, env));
		});
		return {
			body: form,
			contentType
		};
	}
	if (example.body.activeBody === "raw") return {
		body: replaceTemplateVariables(example.body.raw?.value ?? "", env),
		contentType: example.body.raw?.encoding
	};
	if (example.body.activeBody === "binary") return {
		body: example.body.binary,
		contentType: example.body.binary?.type
	};
	return {
		body: void 0,
		contentType: void 0
	};
}
//#endregion
//#region src/libs/send-request/create-fetch-headers.ts
/** Populate the headers from enabled parameters */
function createFetchHeaders(example, env) {
	const headers = {};
	example.parameters.headers.forEach((h) => {
		const lowerCaseKey = h.key.trim().toLowerCase();
		if (h.enabled && (lowerCaseKey !== "content-type" || h.value !== "multipart/form-data")) headers[lowerCaseKey] = replaceTemplateVariables(h.value, env);
	});
	return headers;
}
//#endregion
//#region src/libs/send-request/create-fetch-query-params.ts
/**
* Populate the query parameters from the example parameters. This is an incomplete implementation that currently
* only supports the following styles and options:
*
* - `form` style with `explode` set to true or false
*
* @see https://spec.openapis.org/oas/v3.1.1.html#style-values
*/
function createFetchQueryParams(example, env, request) {
	const params = new URLSearchParams();
	const parameterSchemaMap = (request?.parameters ?? []).reduce((acc, param) => {
		if (param.in === "query") acc[param.name] = param;
		return acc;
	}, {});
	example.parameters.query.forEach((p) => {
		if (!p.enabled) return;
		const schema = parameterSchemaMap[p.key];
		switch (p.type) {
			case "array": {
				const values = replaceTemplateVariables(p.value ?? "", env).split(/,\ ?/);
				if (schema?.explode === false) {
					const csv = values.join(",");
					params.append(p.key, csv);
				} else values.forEach((value) => {
					params.append(p.key, value.trim());
				});
				break;
			}
			default: {
				const value = replaceTemplateVariables(p.value ?? "", env);
				params.append(p.key, value.trim());
				break;
			}
		}
	});
	return params;
}
//#endregion
//#region src/libs/send-request/decode-buffer.ts
/** Decode the buffer according to its content-type */
function decodeBuffer(buffer, contentType) {
	const mimeType = new MimeTypeParser(contentType);
	if (isTextMediaType(mimeType.essence)) return new TextDecoder(mimeType.parameters.get("charset")).decode(buffer);
	return new Blob([buffer], { type: mimeType.essence });
}
//#endregion
//#region src/libs/send-request/create-request-operation.ts
/** Execute the request */
var createRequestOperation = ({ environment, example, globalCookies, proxyUrl, request, securitySchemes, selectedSecuritySchemeUids = [], server, status, pluginManager }) => {
	try {
		const env = environment ?? {};
		const controller = new AbortController();
		/** Parsed and evaluated values for path parameters */
		const pathVariables = example.parameters.path.reduce((vars, param) => {
			if (param.enabled) {
				const substitutedValue = replaceTemplateVariables(param.value, env);
				vars[param.key] = encodeURIComponent(substitutedValue);
			}
			return vars;
		}, {});
		const serverString = replaceTemplateVariables(server?.url ?? "", env);
		const pathString = replaceTemplateVariables(replaceTemplateVariables(request.path, env), pathVariables);
		/**
		* Start building the main URL, we cannot use the URL class yet as it does not work with relative servers
		* Also handles the case of no server with pathString
		*/
		let url = serverString || pathString;
		if (!url) throw ERRORS.URL_EMPTY;
		Object.entries(server?.variables ?? {}).forEach(([k, v]) => {
			url = replaceTemplateVariables(url, { [k]: pathVariables[k] || v.default });
		});
		const _urlParams = createFetchQueryParams(example, env, request);
		const _headers = createFetchHeaders(example, env);
		const { body } = createFetchBody(request.method, example, env);
		const { cookieParams: _cookieParams } = setRequestCookies({
			example,
			env,
			globalCookies,
			serverUrl: url,
			proxyUrl
		});
		const security = buildRequestSecurity(selectedSecuritySchemeUids.flat().map((uid) => securitySchemes[uid]).filter(isDefined$1), env);
		const headers = {
			...Object.entries(security.headers).reduce((acc, [key, value]) => {
				acc[key.toLowerCase()] = value;
				return acc;
			}, {}),
			..._headers
		};
		const cookieParams = [..._cookieParams, ...security.cookies];
		const urlParams = new URLSearchParams([..._urlParams, ...security.urlParams]);
		if (isElectron() && headers["user-agent"]) headers["X-Scalar-User-Agent"] = headers["user-agent"];
		url = mergeUrls$1(url, pathString, urlParams);
		/** Cookie header */
		const cookieHeader = replaceTemplateVariables(getCookieHeader(cookieParams, headers["Cookie"]), env);
		if (cookieHeader) if (isElectron() || shouldUseProxy(proxyUrl, url)) {
			console.warn("We're using a `X-Scalar-Cookie` custom header to the request. The proxy will forward this as a `Cookie` header. We do this to avoid the browser omitting the `Cookie` header for cross-origin requests for security reasons.");
			headers["X-Scalar-Cookie"] = cookieHeader;
		} else {
			console.warn(`We're trying to add a Cookie header, but browsers often omit them for cross-origin requests for various security reasons. If it's not working, that's probably why. Here are the requirements for it to work:

          - The browser URL must be on the same domain as the server URL.
          - The connection must be made over HTTPS.
          `);
			headers["Cookie"] = cookieHeader;
		}
		const proxiedUrl = redirectToProxy(proxyUrl, url);
		const proxiedRequest = new Request(proxiedUrl, {
			method: request.method.toUpperCase(),
			body: body ?? null,
			headers
		});
		const sendRequest = async () => {
			status?.emit("start");
			if (pluginManager) try {
				await pluginManager.executeHook("onBeforeRequest", { request: proxiedRequest });
			} catch (e) {
				const _e = new Error(ERRORS.ON_BEFORE_REQUEST_FAILED, { cause: e });
				status?.emit("abort");
				return [normalizeError(_e), null];
			}
			const startTime = Date.now();
			try {
				const response = await fetch(proxiedRequest, { signal: controller.signal });
				/**
				* Checks if the response is streaming
				* Unfortunately we cannot check the transfer-encoding header as it is not set by the browser so not quite sure how to test when
				* content-type === 'text/plain' and transfer-encoding === 'chunked'
				*
				* Currently we are only checking for server sent events. In OpenApi 3.2.0 streams will be added to the spec
				*/
				const isStreaming = response.headers.get("content-type")?.startsWith("text/event-stream");
				status?.emit("stop");
				const duration = Date.now() - startTime;
				const responseToRead = response.clone();
				const responseHeaders = normalizeHeaders(response.headers, shouldUseProxy(proxyUrl, url));
				const responseType = response.headers.get("content-type") ?? "text/plain;charset=UTF-8";
				const arrayBuffer = await responseToRead.arrayBuffer();
				const responseData = decodeBuffer(arrayBuffer, responseType);
				const clonedResponse = response.clone();
				const statusText = clonedResponse.statusText || httpStatusCodes$1[clonedResponse.status]?.name || "";
				const shouldSkipBody = [
					204,
					205,
					304
				].includes(clonedResponse.status);
				const normalizedResponse = new Response(!shouldSkipBody ? clonedResponse.body : null, {
					status: clonedResponse.status,
					statusText,
					headers: clonedResponse.headers
				});
				if (pluginManager) await pluginManager.executeHook("onResponseReceived", {
					response: normalizedResponse,
					operation: request
				});
				const cookieHeaderKeys = "getSetCookie" in normalizedResponse.headers && typeof normalizedResponse.headers.getSetCookie === "function" ? normalizedResponse.headers.getSetCookie() : [];
				if (isStreaming && response.body) return [null, {
					timestamp: Date.now(),
					request: example,
					response: {
						...normalizedResponse,
						headers: responseHeaders,
						cookieHeaderKeys,
						reader: response.body?.getReader(),
						duration,
						method: request.method,
						path: pathString
					}
				}];
				return [null, {
					timestamp: Date.now(),
					request: example,
					response: {
						...response,
						headers: responseHeaders,
						cookieHeaderKeys,
						data: responseData,
						size: arrayBuffer.byteLength,
						duration: Date.now() - startTime,
						method: request.method,
						status: response.status,
						path: pathString
					}
				}];
			} catch (e) {
				status?.emit("abort");
				return [normalizeError(e, ERRORS.REQUEST_FAILED), null];
			}
		};
		return [null, {
			request: proxiedRequest,
			sendRequest,
			controller
		}];
	} catch (e) {
		console.error(e);
		status?.emit("abort");
		return [normalizeError(e), null];
	}
};
//#endregion
export { ERRORS as a, normalizeHeaders as i, decodeBuffer as n, normalizeError as o, formatHeaderKey as r, prettyErrorMessage as s, createRequestOperation as t };

//# sourceMappingURL=send-request-BsTfRiBC.js.map