import { IS_PATCHED_MODULE } from "./chunk-BZ3Y7YV5.mjs"; import { RequestController, emitAsync, handleRequest } from "./chunk-M4WQE4TR.mjs"; import { Interceptor, RESPONSE_STATUS_CODES_WITH_REDIRECT, createRequestId } from "./chunk-FQQAJBI2.mjs"; // src/interceptors/fetch/index.ts import { invariant } from "outvariant"; import { DeferredPromise } from "@open-draft/deferred-promise"; // src/utils/canParseUrl.ts function canParseUrl(url) { try { new URL(url); return true; } catch (_error) { return false; } } // src/interceptors/fetch/utils/createNetworkError.ts function createNetworkError(cause) { return Object.assign(new TypeError("Failed to fetch"), { cause }); } // src/interceptors/fetch/utils/followRedirect.ts var REQUEST_BODY_HEADERS = [ "content-encoding", "content-language", "content-location", "content-type", "content-length" ]; var kRedirectCount = Symbol("kRedirectCount"); async function followFetchRedirect(request, response) { if (response.status !== 303 && request.body != null) { return Promise.reject(createNetworkError()); } const requestUrl = new URL(request.url); let locationUrl; try { locationUrl = new URL(response.headers.get("location"), request.url); } catch (error) { return Promise.reject(createNetworkError(error)); } if (!(locationUrl.protocol === "http:" || locationUrl.protocol === "https:")) { return Promise.reject( createNetworkError("URL scheme must be a HTTP(S) scheme") ); } if (Reflect.get(request, kRedirectCount) > 20) { return Promise.reject(createNetworkError("redirect count exceeded")); } Object.defineProperty(request, kRedirectCount, { value: (Reflect.get(request, kRedirectCount) || 0) + 1 }); if (request.mode === "cors" && (locationUrl.username || locationUrl.password) && !sameOrigin(requestUrl, locationUrl)) { return Promise.reject( createNetworkError('cross origin not allowed for request mode "cors"') ); } const requestInit = {}; if ([301, 302].includes(response.status) && request.method === "POST" || response.status === 303 && !["HEAD", "GET"].includes(request.method)) { requestInit.method = "GET"; requestInit.body = null; REQUEST_BODY_HEADERS.forEach((headerName) => { request.headers.delete(headerName); }); } if (!sameOrigin(requestUrl, locationUrl)) { request.headers.delete("authorization"); request.headers.delete("proxy-authorization"); request.headers.delete("cookie"); request.headers.delete("host"); } requestInit.headers = request.headers; return fetch(new Request(locationUrl, requestInit)); } function sameOrigin(left, right) { if (left.origin === right.origin && left.origin === "null") { return true; } if (left.protocol === right.protocol && left.hostname === right.hostname && left.port === right.port) { return true; } return false; } // src/interceptors/fetch/index.ts var _FetchInterceptor = class extends Interceptor { constructor() { super(_FetchInterceptor.symbol); } checkEnvironment() { return typeof globalThis !== "undefined" && typeof globalThis.fetch !== "undefined"; } async setup() { const pureFetch = globalThis.fetch; invariant( !pureFetch[IS_PATCHED_MODULE], 'Failed to patch the "fetch" module: already patched.' ); globalThis.fetch = async (input, init) => { const requestId = createRequestId(); const resolvedInput = typeof input === "string" && typeof location !== "undefined" && !canParseUrl(input) ? new URL(input, location.origin) : input; const request = new Request(resolvedInput, init); const responsePromise = new DeferredPromise(); const controller = new RequestController(request); this.logger.info("[%s] %s", request.method, request.url); this.logger.info("awaiting for the mocked response..."); this.logger.info( 'emitting the "request" event for %s listener(s)...', this.emitter.listenerCount("request") ); const isRequestHandled = await handleRequest({ request, requestId, emitter: this.emitter, controller, onResponse: async (response) => { this.logger.info("received mocked response!", { response }); if (RESPONSE_STATUS_CODES_WITH_REDIRECT.has(response.status)) { if (request.redirect === "error") { responsePromise.reject(createNetworkError("unexpected redirect")); return; } if (request.redirect === "follow") { followFetchRedirect(request, response).then( (response2) => { responsePromise.resolve(response2); }, (reason) => { responsePromise.reject(reason); } ); return; } } if (this.emitter.listenerCount("response") > 0) { this.logger.info('emitting the "response" event...'); await emitAsync(this.emitter, "response", { // Clone the mocked response for the "response" event listener. // This way, the listener can read the response and not lock its body // for the actual fetch consumer. response: response.clone(), isMockedResponse: true, request, requestId }); } Object.defineProperty(response, "url", { writable: false, enumerable: true, configurable: false, value: request.url }); responsePromise.resolve(response); }, onRequestError: (response) => { this.logger.info("request has errored!", { response }); responsePromise.reject(createNetworkError(response)); }, onError: (error) => { this.logger.info("request has been aborted!", { error }); responsePromise.reject(error); } }); if (isRequestHandled) { this.logger.info("request has been handled, returning mock promise..."); return responsePromise; } this.logger.info( "no mocked response received, performing request as-is..." ); return pureFetch(request).then((response) => { this.logger.info("original fetch performed", response); if (this.emitter.listenerCount("response") > 0) { this.logger.info('emitting the "response" event...'); const responseClone = response.clone(); this.emitter.emit("response", { response: responseClone, isMockedResponse: false, request, requestId }); } return response; }); }; Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, { enumerable: true, configurable: true, value: true }); this.subscriptions.push(() => { Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, { value: void 0 }); globalThis.fetch = pureFetch; this.logger.info( 'restored native "globalThis.fetch"!', globalThis.fetch.name ); }); } }; var FetchInterceptor = _FetchInterceptor; FetchInterceptor.symbol = Symbol("fetch"); export { FetchInterceptor }; //# sourceMappingURL=chunk-T5YM7SUV.mjs.map